您的位置:首页 > 其它

2017.7.26 机房测试(KMP/Hash,分块+可持久化Trie树贪心,主席树+AC自动机)

2017-07-26 20:38 253 查看

1 无尽的矩阵(matrix.c/cpp/pas)

1.1 题目描述

从前有一个的小矩阵,矩阵的每个元素是一个字母(区分大小写),突然有一天它发生了变异,覆盖了整个二维空间,即不停自我复制产生相同的矩阵然后无隙放置。现在二维空间已经被它占领了,但你只被告知了大小为R*C空间的内容(可能包含不完整的原矩阵),为了将它恢复原状,你需要找到满足条件的面积最小的原矩阵。

奇怪的是,同时有 T 个二维空间发生了变异,你需要尽快解决这些变异。

1.2 输入格式

第一行为一个整数T,表示二维空间数目。

接下来T组数据。每组数据第一行包含两个数 R,C,表示你被告知的空间大小;接下来 R 行,每行包含

C 个字母,表示你被告知的空间内容。

1.3 输出格式

对于每一组数据输出一行,每行只包含一个数,表示最小的原矩阵面积。

1.4 样例输入

2

2 5

ABABA

ABABA

2 8

ABCDEFAB

AAAABAAA

1.5 样例输出

2

12

1.6 数据范围与约定

对于前20%的数据R<=20,C<=20;

对于前40%的数据R<=400,C<=100;

对于100%的数据R<=5000 ,C<=100,T<=50。

【解题报告】

做法1:hash求出每一行可能的循环节长度,取公共的最小循环节长度即可,列同理。将两次求得的最小循环节长度相乘即为答案。较慢,可能会超时。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<ctime>
using namespace std;

unsigned long long base1=1000000007 , base2=998244353 , pow1[10005] , pow2[10005] , h[10005][105] ;
int R , C ;
char mat[10005][105] ;

char get_c(){
char c;
while((c=(char)getchar())!=EOF) if(!isspace(c)) break ;
return c;
}
unsigned long long get_h(int x1,int y1,int x2,int y2){
return h[x2][y2]-h[x1-1][y2]*pow1[x2-x1+1]-h[x2][y1-1]*pow2[y2-y1+1]+h[x1-1][y1-1]*pow1[x2-x1+1]*pow2[y2-y1+1] ;
}
bool check(int a,int b){
for(int i=1;i<=R;i+=a){
for(int j=1;j<=C;j+=b){
int ti=min(i+a-1,R) , tj=min(j+b-1,C) ;
if(get_h(1,1,ti-i+1,tj-j+1)!=get_h(i,j,ti,tj)) return false ;
}
}
return true ;
}
void solve(){
scanf("%d%d",&R,&C) ;
memset(h,0,sizeof(h)) ;
for(int i=1;i<=R;++i) for(int j=1;j<=C;++j) mat[i][j]=get_c() , h[i][j]=h[i-1][j]*base1+h[i][j-1]*base2-h[i-1][j-1]*base1*base2+mat[i][j] ;
int ans=(1<<30)-1 ;
for(int i=1;i<=R;++i){
for(int j=1;j<=C;++j){
if(i*j>=ans) continue ;
if(check(i,j)) ans=i*j ;
}
}
cout << ans << '\n' ;
}
int main(){
freopen("matrix.in","r",stdin) ;
freopen("HASH_matrix.out","w",stdout) ;
int T;
pow1[0]=pow2[0]=1 ;
for(int i=1;i<=10000;++i) pow1[i]=pow1[i-1]*base1 ;
for(int i=1;i<=10000;++i) pow2[i]=pow2[i-1]*base2 ;
scanf("%d",&T) ;
while(T--) solve() ;
fprintf(stderr,"%d\n",clock()) ;
return 0 ;
}


做法 2:将每一行hash为一个数,对得到的新数组直接跑KMP求最小循环节长度,列同理。将两次求得的最小循环节长度相乘即为答案。这就是std做法。

代码如下:

#include<iostream>
#include<ctime>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int R , C ;
char A[10005][105] ;
unsigned long long base=100007 , h_r[10005] , h_c[10005] , zj[10005] ;
int len , fail[10005] ;
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] ;
}
void solve(){
scanf("%d%d",&R,&C);
memset(h_r,0,sizeof(h_r)) ; memset(h_c,0,sizeof(h_c)) ;
for(int i=1;i<=R;++i) for(int j=1;j<=C;++j) A[i][j]=get_c() , h_r[i]=h_r[i]*base+A[i][j] ;
for(int j=1;j<=C;++j) for(int i=1;i<=R;++i) h_c[j]=h_c[j]*base+A[i][j] ;
for(int i=1;i<=R;++i) zj[i]=h_r[i] ;
len=R ;
int ans=get_it() ;
for(int i=1;i<=C;++i) zj[i]=h_c[i] ;
len=C ;
ans*=get_it() ;
cout << ans << '\n' ;
}
int main(){
freopen("matrix.in","r",stdin) ;
freopen("matrix.out","w",stdout) ;
int T;
scanf("%d",&T) ;
for(int i=1;i<=T;++i) solve() ;
//fprintf(stderr,"std: %d\n",clock()) ;
return 0 ;
}


做法3,水过去,可能是数据太水了

然而最后被卡掉了,(可以试一试随机找一个位置开始for或者倒着for,应该比较难卡)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

int t,R,C,nxt[5005];
char s[5005][105], tp[5005];

int Kmp(char *s,int len)
{
memset(nxt,0,sizeof(nxt));
int i=0,j=-1;
nxt[0]=-1;
while(i<len)
{
if(j==-1||s[i]==s[j])
{
++i;++j;
nxt[i]=j;
}
else j=nxt[j];
}
return len-nxt[len];
}
int main()
{
freopen( "matrix.in", "r", stdin );
freopen( "matrix.out", "w", stdout );
for(scanf("%d",&t);t;--t)
{
scanf("%d%d",&R,&C);
for(int i=1;i<=R;++i)
scanf("%s",s[i]);
int ans1=0;
for(int i=1;i<=R;++i)
ans1=max(ans1,Kmp(s[i],C));
int ans2=0;
for(int i=1;i<=C;++i)
{
for(int j=1;j<=R;++j) tp[j-1]=s[j][i];
ans2=max(ans2,Kmp(tp,R));
}
printf("%d\n",ans1*ans2);
}
return 0;
}


2 异或(xor.c/cpp/pas)

2.1 题目描述

给出 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 )

2.2 输入格式

第一行为两个整数n,m,分别表示数的个数和询问次数。

接下来一行 n个数,再接下来 m行,每行两个数 x,y,表示给出询问(x,y),通过上述操作得到l和r,查询[l,r]中最大连续异或和。

2.3 输出格式

输出m行,每行一个整数表示该次询问的答案。

2.4 样例输入

3 3

1 4 3

0 1

0 1

4 3

2.5 样例输出

5

7

7

2.6 数据范围与约定

对于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>
using namespace std;
#define maxn 12005
#define maxbit 31

int N,M,A[maxn];
int tr[maxn];

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-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);
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+last)%N+1;
r=(r+last)%N+1;
last=GetAns(l,r);
printf("%d\n",last);
}
return 0;
}


[b]3魔法串(magic.c/cpp/pas)

3.1 题目描述

给你一棵n+1个结点的有根树,结点从0到n标号,其中0为根结点。

这是一棵魔法树。这棵树的每条边有一个魔力值,同一个结点连向不同子结点的边的魔力值不同。一个结点所代表的魔法串是从根一直走到这个结点,经过的魔力值依次排列形成的有序序列,另外,一个串是魔法串当且仅当它被一个结点所代表。

现在,为了使用强大的魔法,你需要对每个魔法串,找到最长的是它后缀的魔法串。为了方便输出,你只需要输出代表这个魔法串的结点的标号即可。若没有这个魔法串,请输出0。

3.2 输入格式

第一行一个整数n,代表除根以外的结点个数。

第二行 n个整数,第i个整数P_i代表标号为i的结点的父亲标号。

第三行 n个整数,第i个整数C_i代表标号为i的结点连向父亲的边的魔力值。

3.3 输出格式

输出一行n个整数,第i个整数表示第i个结点代表的魔法串的答案。

3.4 样例输入

7

0 0 1 1 2 4 5

1 2 3 2 1 1 3

3.5 样例输出

0 0 02 1 5 3

3.6 数据范围与约定

对于30%的数据,保证1<=n<=2000。

对于100%的数据,保证1<=n<=200000,0<=P_i

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 200010
#define M 6000010

struct Edge {int p,v,w;}e
;
struct Node {int l,r,v;}t[M];
int f
,q
,ql,qr,rt
,n,p
,c
,hed
,e_cnt,tot;

void addedge(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)>>1;
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);
return query(t[x].r,mid+1,r,p);
}
int main()
{
freopen("magic.in","r",stdin);
freopen("magic.out","w",stdout);
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&p[i]);
for(int i=1;i<=n;++i) scanf("%d",&c[i]),addedge(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]);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: