第一题
无尽的矩阵(matrix.c/cpp/pas)
题目描述
从前有一个的小矩阵,矩阵的每个元素是一个字母(区分大小写),突然有一天它发生了变异,覆盖了整个二维空间,即不停自我复制产生相同的矩阵然后无隙放置。现在二维空间已经被它占领了,但你只被告知了大小为R*C空间的内容(可能包含不完整的原矩阵),为了将它恢复原状,你需要找到满足条件的面积最小的原矩阵。
奇怪的是,同时有 T 个二维空间发生了变异,你需要尽快解决这些变异。
输入格式
第一行为一个整数T,表示二维空间数目。
接下来T组数据。每组数据第一行包含两个数 R,C,表示你被告知的空间大小;接下来 R 行,每行包含 C 个字母,表示你被告知的空间内容。
输出格式
对于每一组数据输出一行,每行只包含一个数,表示最小的原矩阵面积。
样例输入
2
2 5
ABABA
ABABA
2 8
ABCDEFAB
AAAABAAA
样例输出
2
12
数据范围与约定
对于前20%的数据R<=20,C<=20;
对于前40%的数据R<=400,C<=100;
对于100%的数据R<=5000 ,C<=100,T<=50。
思路
做法1:hash求出每一行可能的循环节长度,取公共的最小循环节长度即可,列同理。将两次求得的最小循环节长度相乘即为答案。较慢,可能会超时。
做法 2:将每一行hash为一个数,对得到的新数组直接跑KMP求最小循环节长度,列同理。将两次求得的最小循环节长度相乘即为答案。这就是std做法。
代码
#include<iostream>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=10000+5;
char A[N][105];
unsigned long long base=100007,r[N],c[N],zj[N] ;
int R,C,len,fail[N];
char get_c()
{
char c;
while((c=(char)getchar())!=EOF) if (!isspace(c)) break;
return c;
}
int get_it()
{
memset(fail,0,sizeof(fail));
for (int i=2;i<=len;++i)
{
int t=fail[i-1];
while(t&&zj[t+1]!=zj[i]) t=fail[t];
if(zj[t+1]==zj[i]) fail[i]=t+1;
}
return len-fail[len];
}
int main()
{
freopen("matrix.in","r",stdin) ;
freopen("matrix.out","w",stdout) ;
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&R,&C);
memset(r,0,sizeof(r));
memset(c,0,sizeof(c));
for(int i=1;i<=R;i++)
for(int j=1;j<=C;j++)
{
A[i][j]=get_c();
r[i]=r[i]*base+A[i][j];
}
for(int j=1;j<=C;j++)
for(int i=1;i<=R;i++)
c[j]=c[j]*base+A[i][j];
for(int i=1;i<=R;++i)
zj[i]=r[i];
len=R;
int ans=get_it();
for(int i=1;i<=C;++i)
zj[i]=c[i];
len=C;
ans*=get_it();
printf("%d\n",ans);
}
return 0 ;
}
第二题
异或(xor.c/cpp/pas)
题目描述
给出 n 个数,Q次询问,每次问[l,r]中最大连续异或和。
为了体现在线操作,对于每次询问(x,y):
l=min( ((x+lastans) mod n)+1 , ((y+lastans) mod n)+1 )
r=max( ((x+lastans) mod n)+1 , ((y+lastans) mod n)+1 )
输入格式
第一行为两个整数n,m,分别表示数的个数和询问次数。
接下来一行 n个数,再接下来 m行,每行两个数 x,y,表示给出询问(x,y),通过上述操作得到l和r,查询[l,r]中最大连续异或和。
输出格式
输出m行,每行一个整数表示该次询问的答案。
样例输入
3 3
1 4 3
0 1
0 1
4 3
样例输出
5
7
7
数据范围与约定
对于30%的数据,n<=500,Q<=500。
对于100%的数据,n<=12000 , Q<=6000 , 给出的数均在signed longint 范围内。
思路
将 n 个数分成sqrt(n)个块。考虑用 w[i][j] 表示从第 i 个块开头元素到第 j 个元素这个区间中,最大连续异或和。建可持久化Trie树并且预处理出w数组。预处理复杂度为 O(n * sqrt(n) * 位数)。
查询[l,r]时,令 p 为 l 以右第一个块开头元素,那么首先可以直接得到 p 到 r 区间的答案。再考虑上 [l,p-1] 区间中的元素,逐个在可持久化Trie上贪心即可。查询总复杂度为O(Q * sqrt(n) * 位数)。
代码
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
typedef long long LL;
const int maxn=12005;
const int maxbit=31;
int N,M,A[maxn],tr[maxn];
int max(int x,int y)
{
if (x>=y) return x;
else return y;
}
struct PerTrie
{
int next[10000005][2],num[10000005];
int id;
void init() {id=next[0][0]=next[0][1]=num[0]=0;}
int f(int x,int i){return (x>>i)&1;}
void Insert(int& rt,int pre,int x,int pos) //插入
{
rt=++id;
next[rt][0]=next[pre][0];
next[rt][1]=next[pre][1];
num[rt]=num[pre]+1;
if (pos==-1) return ;
int d=f(x,pos);
Insert(next[rt][d],next[pre][d],x,pos-1);
}
int MaxXor(int l,int r,int x) //查询最大异或值,因为A[i]保存
{ //的是前缀异或值,所以得到的结果就是某一段区间的异或值
int ret=0;
for(int i=maxbit;i>=0;i--)
{
int d=f(x,i);
int a=next[l][d^1],b=next[r][d^1];
if(num[b]-num[a]>0) ret|=(1<<i),l=a,r=b;
else l=next[l][d],r=next[r][d];
}
return ret;
}
}PT;
int block,num,bel[maxn],dp[120][maxn];
//dp保存第几块到第几个数的区间最大异或值
void init()
{
tr[0]=0;
PT.init();
for(int i=1;i<=N;i++) PT.Insert(tr[i],tr[i-1],A[i],maxbit); //插入
block=(int)sqrt(N+0.5);
num=N/block;
if(N%block) num++; //加1
memset(dp,0,sizeof(dp));
bel[0]=0;
for(int i=1;i<=N;i++) bel[i]=(i-1)/block+1; //记录下属于哪个块
for(int i=1;i<=num;i++)
{
int st=(i-1)*block+1;
for(int j=st;j<=N;j++)
{
dp[i][j]=max(dp[i][j-1],A[j]^A[st-1]);//可能是[st,j]这段
dp[i][j]=max(dp[i][j],PT.MaxXor(tr[st-1],tr[j],A[j]));
//再找最大的
}
}
}
int GetAns(int l,int r)
{
l--;
int s=bel[l],ret=0;
if (bel[r]>s) ret=dp[s+1][r]; //查询从后面一个块开始的
for (int i=l;i<=min(r,s*block);i++)
ret=max(ret,PT.MaxXor(tr[l-1],tr[r],A[i]));
return ret;
}
int main()
{
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
scanf("%d%d",&N,&M);
A[0]=0;
int x;
for(int i=1;i<=N;i++)
{
scanf("%d",&x);
A[i]=A[i-1]^x;
}
init();
int last=0,l,r;
while(M--)
{
scanf("%d%d",&l,&r);
l=(l+(LL)last)%N+1;
r=(r+(LL)last)%N+1;
if(l>r) swap(l,r);
//printf("%d %d\n",l,r);
last=GetAns(l,r);
printf("%d\n",last);
}
return 0;
}
第三题
魔法串(magic.c/cpp/pas)
题目描述
给你一棵n+1个结点的有根树,结点从0到n标号,其中0为根结点。
这是一棵魔法树。这棵树的每条边有一个魔力值,同一个结点连向不同子结点的边的魔力值不同。一个结点所代表的魔法串是从根一直走到这个结点,经过的魔力值依次排列形成的有序序列,另外,一个串是魔法串当且仅当它被一个结点所代表。
现在,为了使用强大的魔法,你需要对每个魔法串,找到最长的是它后缀的魔法串。为了方便输出,你只需要输出代表这个魔法串的结点的标号即可。若没有这个魔法串,请输出0。
输入格式
第一行一个整数n,代表除根以外的结点个数。
第二行 n个整数,第i个整数P_i代表标号为i的结点的父亲标号。
第三行 n个整数,第i个整数C_i代表标号为i的结点连向父亲的边的魔力值。
输出格式
输出一行n个整数,第i个整数表示第i个结点代表的魔法串的答案。
样例输入
7
0 0 1 1 2 4 5
1 2 3 2 1 1 3
样例输出
0 0 0 2 1 5 3
数据范围与约定
对于30%的数据,保证1<=n<=2000。
对于100%的数据,保证1<=n<=200000,0<=P_i
思路
考虑补全AC自动机(Trie图),考虑一个结点u所连出的转移边与fail[u]所连出的转移边的关系,只有u直接连出的边会影响这些转移边,而边数是n-1条。于是我们考虑将fail[u]的转移边全部复制给u,再在此基础上对u的转移边进行修改。这个如何实现?用可持久化线段树维护每个结点的转移边即可。
代码
#include<bits/stdc++.h>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
using namespace std;
const int N=200005;
const int M=6000005;
typedef long long LL;
struct edge
{
int p, v, w;
}e[N];
struct node
{
int l, r, v;
}t[M];
int f[N],q[N],ql,qr,rt[N],n,p[N],c[N],hed[N],e_cnt,tot;
int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
void build(int x,int y,int w)
{
e[++e_cnt].p=y;
e[e_cnt].v=hed[x];
e[e_cnt].w=w;
hed[x]=e_cnt;
}
void modify(int &x,int l,int r,int p,int v)
{
int y=x;
x=++tot;
t[x]=t[y];
if (l==r) {t[x].v=v;return ;}
int mid=(l+r)/2;
if (p<=mid) return modify(t[x].l,l,mid,p,v);
else return modify(t[x].r,mid+1,r,p,v);
}
int query(int x,int l,int r,int p)
{
if (l==r) return t[x].v;
int mid=l+r>>1;
if (p<=mid) return query(t[x].l,l,mid,p);
else return query(t[x].r,mid+1,r,p);
}
int query(int x,int l,int r,int p)
{
if (l==r) return t[x].v;
int mid=(l+r)/2;
if (p<=mid) return query(t[x].l,l,mid,p);
else return query(t[x].r,mid+1,r,p);
}
int main()
{
// freopen("magic.in","r",stdin);
// freopen("magic.out","w",stdout);
n=read();
for (int i=1;i<=n;i++)
p[i]=read();
for (int i=1;i<=n;i++)
{
c[i]=read();
build(p[i],i,c[i]);
}
for (int i=1;i<=n;i++)
{
c[i]=read();
build(p[i],i,c[i]);
}
for (q[qr++]=0;ql^qr;ql++)
{
int x=q[ql];
rt[x]=rt[f[x]];
for (int i=hed[x];i;i=e[i].v)
{
f[q[qr++]=e[i].p]=query(rt[f[x]],1,n,e[i].w);
modify(rt[x],1,n,e[i].w,e[i].p);
}
}
for (int i=1;i<=n;i++)
{
printf("%d",f[i]);
if (n==i) printf("\n");
else printf(" ");
}
return 0;
}