您的位置:首页 > 其它

codeforces 722F. Cyclic Cipher

2016-10-27 23:38 169 查看
题目链接http://codeforces.com/problemset/problem/722/F

题目大意:给定n个数列,第 i 个数列包含ki个不超过m的正整数,同一数列里的数互不相同。每一秒将n个数列中的数左移一个位置,每个数列第一个数则移到该数列最后,并在一张纸上记下每个数列的第一个数。10^100秒过后,对于所有的1<=x<=m,求x在纸上出现的最长的连续的一段长度,该段必须是同一秒中记下的数。

数据范围:1 ≤ n, m ≤ 100 000,1 ≤ ki ≤ 40,∑ki<=200 000

题解:由于题目所求的是一段连续的区间,所以对于一个数x,如果它在一段连续的数列出现,并且找到一个时刻使得它们同时被写下,那么这就是一段连续的x。

所以我们的任务是如何判断它们能否在同一秒被写下。

1.exgcd+倍增预处理

对于第 i 个数列的第 bi 个数(bi∈[0,ki)),如果它在第t秒被记下,那么t应满足t=ki*x+bi。所以我们应该判断的是能否找到一个时刻t能同时表示成k1*x1+b1以及k2*x2+b2的形式,即能否找到x1,x2使得k1*x1+b1=k2*x2+b2,移项k1*x1-k2*x2=b2-b1,这就是一个不定方程,可以用扩展欧几里得求解,然后利用中国剩余定理将两个表达式合并成一个新的表达式k3*x+b3。于是方法就出来了,用倍增预处理出u[i][j]表示从i开始能否合并到i+2^j-1,然后我们枚举左端点,判断能达到的最远的右端点即可。

时间复杂度O(nlognlogLCM)

2.gcd+左右指针

事实上本题还有一个更简便的判断方法。我们知道对于两个表达式k1*x+b1和k2*x+b2,如果(b1-b2) mod gcd(k1,k2)=0,那么方程k1*x1-k2*x2=b2-b1就一定有解。所以判断一段连续数列的表达式可不可行只要判断它们是不是两两有解就可以了。我们知道,当k1=k2时,b1必须等于b2,而k<=40,所以我们判断一个新的表达式是否与前面的表达式冲突(即无解),只要判断不超过40次就可以了。于是我们记d[ i ]表示当k=i时b的值,f[ i ]表示个数,每次判断一个新的k和b,只要判断当f[ i ]>0时(d[ i ]-b) mod gcd(k,i)是否为0。设置两个左右指针 l 和 r ,每次对于当前的 l ,把 r 尽量往右移直到冲突为止。同时把没有冲突的k和b加入到相应的 f 和 d 中,l+1时只要把 f[k[l]] 减1就可以了。

时间复杂度O(40nlog40)

代码如下:

1.exgcd+倍增预处理

#include <algorithm>
#include <cstdio>
typedef long long ll;
int k[100005],a[200005],b[200005],ne[200005],fi[100005],
c[100005],u[200005][20],tot=0,ans;
ll f[100005][20],g[100005][20],x0,y0,d;
void add(int x,int y,int z)
{
a[++tot]=y;b[tot]=z;ne[tot]=fi[x];fi[x]=tot;
}
void exgcd(ll a,ll b,ll c)
{
if (!b)
{
if (c%a) d=-1;
else d=a;
x0=c/a;y0=0;
return;
}
exgcd(b,a%b,c);
ll t=x0;x0=y0;y0=t-a/b*y0;
}
void solve(int l,int r)
{
for (int i=l;i<=r;i++) f[i][0]=k[a[c[i]]],g[i][0]=b[c[i]],u[i][0]=1;
for (int i=r,j;i>=l;i--)
{
for (j=1;i+(1<<j)-1<=r && u[i+(1<<(j-1))][j-1];j++)
{
ll a0=f[i][j-1],b0=f[i+(1<<(j-1))][j-1],
c0=g[i+(1<<(j-1))][j-1]-g[i][j-1];
exgcd(a0,b0,c0);
if (d==-1) break;
b0/=d;x0=(x0%b0+b0)%b0;
f[i][j]=b0*a0;
g[i][j]=(x0*a0+g[i][j-1])%f[i][j];
u[i][j]=1;
}
for (;i+(1<<j)-1<=r;j++) u[i][j]=0;
}
for (int i=l;i<=r;i++)
{
int j=0,x=0;
for (;i+(1<<j)-1<=r && u[i][j];j++);
j--;x=(1<<j);int y=i+(1<<j);ll F=f[i][j],G=g[i][j];
for (;y<=r;y+=1<<j,x+=1<<j)
{
for (;y+(1<<j)-1>
4000
;r || !u[y][j];j--);
for (;j>=0;j--)
{
ll a0=F,b0=f[y][j],c0=g[y][j]-G;
exgcd(a0,b0,c0);
if (d==-1) continue;
b0/=d;x0=(x0%b0+b0)%b0;
F=b0*a0;
G=(x0*a0+G)%F;
break;
}
if (j<0) break;
}
ans=std::max(ans,x);
}
}
int main()
{
int n,m;
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&k[i]);
for (int j=0;j<k[i];j++)
{
int x;
scanf("%d",&x);
add(x,i,j);
}
}
for (int i=1;i<=m;i++)
{
int len=0;ans=0;
for (int j=fi[i];j;j=ne[j]) c[++len]=j;
for (int j=1,k;j<=len;j=k)
{
for (k=j+1;k<=len && a[c[k-1]]-1==a[c[k]];k++);
solve(j,k-1);
}
printf("%d\n",ans);
}
}


2.gcd+左右指针

#include <algorithm>
#include <cstdio>
int k[100005],a[200005],b[200005],ne[200005],fi[100005],
c[100005],d[50],f[50],tot=0;
void add(int x,int y,int z)
{
a[++tot]=y;b[tot]=z;ne[tot]=fi[x];fi[x]=tot;
}
int gcd(int x,int y)
{
return y?gcd(y,x%y):x;
}
bool check(int k,int x)
{
for (int i=1;i<=40;i++)
if (f[i] && (d[i]-x)%gcd(k,i)) return 0;
d[k]=x;f[k]++;
return 1;
}
int main()
{
int n,m;
scanf("%d%d\n",&n,&m);
for (int i=1;i<=n;i++)
{
scanf("%d",&k[i]);
for (int j=0;j<k[i];j++)
{
int x;
scanf("%d",&x);
add(x,i,j);
}
}
for (int i=1;i<=m;i++)
{
int len=0,ans=0;
for (int j=fi[i];j;j=ne[j]) c[++len]=j;
for (int j=1;j<=40;j++) f[j]=0;
for (int l=1,r=1;r<=len;l++)
{
for (;r<=len && a[c[l]]-a[c[r]]==r-l;r++)
if (!check(k[a[c[r]]],b[c[r]])) break;
ans=std::max(ans,r-l);
f[k[a[c[l]]]]--;
}
printf("%d\n",ans);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  codeforces