Codeforces Round 323 (Div. 1) (CF582)
A. GCD Table
首先有个结论,\(\gcd(x,y)\le \min(x,y)\)。
那么给出的数组中最大的数一定属于原来的 \(a\)。把它排除掉后,次大的数同理。因此,我们每次找到剩下最大的数加入 \(a\) 数组,并把 \(a\) 数组中已经形成的 \(\gcd\) 依次删除,不断重复这个过程即可构造出答案。
Code
const int N=2.5e5+5;
int n,b[N],a[N],flag[N];
map<int,int> pos;
int main()
{
n=read();
for(int i=1;i<=n*n;i++) b[i]=read();
sort(b+1,b+n*n+1),reverse(b+1,b+n*n+1);
for(int i=n*n;i;i--) pos[b[i]]=i;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n*n;j++) if(!flag[j]) {a[i]=b[j];break;}
flag[pos[a[i]]]=1,pos[a[i]]++;
for(int j=1;j<i;j++)
{
int x=__gcd(a[i],a[j]);
flag[pos[x]]=1,pos[x]++;flag[pos[x]]=1,pos[x]++;
}
}
for(int i=1;i<=n;i++) printf("%d ",a[i]);printf("\n");
return 0;
}
B. Once Again...
一个不太动脑子的做法是定义广义矩乘,然后矩阵快速幂转移 dp。
当然题这么做就没意思了,所以来写写另外一种做法。
观察到,如果 \(T\) 很大,一定会在某一个区间中一直重复选同一个数。那么答案可以分成两部分:
- 正常的最长不降子序列
- 一直重复的部分
对于前者,我们把序列每 \(n\) 个划成一段。显然不可能存在某一段一个都不选,因为这样不如把这段丢到第二种情况。
那么因为钦定了每段都要选,只考虑前 \(n\) 段一定包含了最优解。
而剩下的 \(T-n\) 段,我们希望重复的数尽量多。所以选择出现次数最多的数,一直重复选它就可以了。
Code
const int N=1e4+5;
int n,T,a[N];
int cnt[N],mx,ans,f[N];
int main()
{
n=read(),T=read();
for(int i=1;i<=n;i++) a[i]=read();
int len=min(n,T)*n;
for(int i=n+1;i<=len;i++) a[i]=a[i-n];
for(int i=1;i<=len;i++)
for(int j=0;j<i;j++) if(a[j]<=a[i]) f[i]=max(f[i],f[j]+1),ans=max(ans,f[i]);
for(int i=1;i<=n;i++) cnt[a[i]]++,mx=max(mx,cnt[a[i]]);
printf("%d\n",ans+max(T-n,0)*mx);
return 0;
}
C. Superior Periodic Subarrays
题目中的限制等价于 \(a_i\ge a_{(ks+i)\bmod n}\)。
推推式子,发现这可以整理成 \(a_i\ge a_{x\gcd(s,n)+i}\)。
也就是说,我们枚举 \(\gcd(s,n)\),并把 \(a\) 按 \(\bmod \, \gcd(s,n)\) 的值分成 \(d\) 组。那么每个组能选的 \(a_i\) 只有这个组内的最大值。
处理出所有可以选择的数,令 \(f_i\) 表示以 \(i\) 结尾最多连续选几个。答案就是可以选的区间数。
然后一个很奇怪的优化是,判断 \(\gcd(s,n)=d\) 比判断 \(\gcd(\frac{s}{d},\frac{n}{d})=1\) 要慢,写成前面那种会被卡常。感觉很离谱。
Code
#define int long long
const int N=1e6+5,mod=998244353;
int n,a[N];
int ans,b[N],f[N],mx[N],cnt[N];
signed main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
for(int d=1;d<n;d++)
{
if(n%d) continue;
for(int k=0;k<d;k++)
{
mx[k]=0;
for(int i=k;i<(n<<1);i+=d) mx[k]=max(mx[k],a[i]);
for(int i=k;i<(n<<1);i+=d) b[i]=(a[i]==mx[k]);
}
f[0]=b[0];
for(int i=1;i<(n<<1);i++) f[i]=min(n-1,b[i]?f[i-1]+1:0);
cnt[0]=0;
for(int i=1;i<(n/d);i++) cnt[i]=cnt[i-1]+(__gcd(i,n/d)==1);
for(int i=n;i<(n<<1);i++) ans+=cnt[f[i]/d];
}
printf("%lld\n",ans);
return 0;
}
D. Number of Binominal Coefficients
根据库默尔定理,有:\(\binom{n+m}{m}\) 中,质数 \(p\) 的幂次等于 \(n,m\) 在 \(p\) 进制下相加的进位次数。
那么题意转化为计算有多少对 \((n,m)\),\(n+m\le A\),它们在 \(p\) 进制下相加的进位次数 \(\ge \alpha\)。
考虑数位 dp:设 \(f_{i,0/1,0/1,j}\) 表示前 \(i\) 位,是否进位,是否卡上界,进位了 \(j\) 次的方案数。
这东西会炸空间,要开滚动数组。
Code
#define int long long
const int N=4005,mod=1e9+7;
int p,c,a[N],b[N];
int f[2][2][N],g[2][2][N];
char s[N];
int n;
il void add(int &x,int y) {x=(x+y)%mod;}
signed main()
{
p=read(),c=read(); scanf("%s",s+1),n=strlen(s+1);
for(int i=1;i<=n;i++) a[i]=s[i]-'0';
reverse(a+1,a+n+1);
for(int i=n;i;i--)
{
for(int j=1;j<N-1;j++) b[j]*=10;
b[1]+=a[i];
for(int j=1;j<N-1;j++) b[j+1]+=b[j]/p,b[j]%=p;
}
int n=N-1;
while(!b[n]&&n) n--;
if(!n) {printf("0\n");return 0;}
// cout<<"??"<<n<<endl;
for(int i=1;i<=n;i++) a[i]=b[i];
// cout<<endl;
f[1][0][0]=1;
int a0=(p*(p+1)/2)%mod;
int a1=((p-1)*p/2)%mod;
for(int i=n;i;i--)
{
int a2=a[i]*(a[i]+1)/2%mod,a3=a[i]*(a[i]-1)/2%mod;
int a4=a[i]*(2*p-a[i]-1)/2%mod,a5=a[i]*(2*p-a[i]+1)/2%mod;
memset(g,0,sizeof(g));
for(int j=0;j<=n-i;j++)
{
add(g[0][0][j],f[0][0][j]*a0+f[1][0][j]*a2);
add(g[0][1][j],f[0][0][j]*a1+f[1][0][j]*a3);
add(g[1][0][j],f[1][0][j]*(a[i]+1));
add(g[1][1][j],f[1][0][j]*a[i]);
add(g[0][0][j+1],f[0][1][j]*a1+f[1][1][j]*a4);
add(g[0][1][j+1],f[0][1][j]*a0+f[1][1][j]*a5);
add(g[1][0][j+1],f[1][1][j]*(p-a[i]-1));
add(g[1][1][j+1],f[1][1][j]*(p-a[i]));
}
swap(f,g);
}
int res=0;
for(int i=c;i<=n;i++)
{
add(res,f[0][0][i]+f[1][0][i]);
}
printf("%lld\n",res);
return 0;
}
E. Boolean Function
先建出表达式树。
考虑 dp,设 \(f_{i,j}\) 表示合并到点 \(i\),子树内每个变量的计算结果状压后为 \(j\) 的方案数。
那么转移方程就是:
直接转移是不可行的,但是这个式子貌似长得和位运算卷积一模一样。套一个 FMT 优化就能过。
Code
typedef long long ll;
const int N=505,M=(1<<16)+5,mod=1e9+7;
il void add(int &x,ll y) {x=(x+y%mod)%mod;if(x<0) x=(x+mod)%mod;}
int n,m,S,limit;
char s[N];
int f[N<<1][M],g[M];
struct node
{
int f[4];
}a[25];
void Or(int *a,int tp)
{
for(int len=1;len<limit;len<<=1)
for(int i=0;i<limit;i+=(len<<1))
for(int j=0;j<len;j++) add(a[i+j+len],a[i+j]*tp);
}
void And(int *a,int tp)
{
for(int len=1;len<limit;len<<=1)
for(int i=0;i<limit;i+=(len<<1))
for(int j=0;j<len;j++) add(a[i+j],a[i+j+len]*tp);
}
int tot;
int L[N],R[N];
#define ls L[now]
#define rs R[now]
void solve(int &now,int l,int r)
{
if(!now) now=++tot;
if(l>r) return;
if(l==r)
{
if(s[l]!='?')
{
int ss=0;
for(int i=0;i<m;i++)
{
int now=0;
if(s[l]>='A'&&s[l]<='D') now=a[i].f[s[l]-'A'];
else now=a[i].f[s[l]-'a']^1;
ss|=(1<<i)*now;
}
f[now][ss]++;
}
else
{
for(int c=0;c<4;c++)
{
int s1=0,s2=0;
for(int i=0;i<m;i++)
{
int now=a[i].f[c];
s1|=(1<<i)*now,s2|=(1<<i)*(now^1);
}
f[now][s1]++,f[now][s2]++;
}
}
return;
}
int sum=0,mid=0;
for(int i=l;i<=r;i++)
if(s[i]=='(') sum++;
else if(s[i]==')') sum--;
else if(!sum) mid=i;
solve(ls,l+1,mid-2),solve(rs,mid+2,r-1);
memset(g,0,sizeof(g));
if(s[mid]!='|')
{
And(f[ls],1),And(f[rs],1);
for(int i=0;i<limit;i++) add(g[i],1ll*f[ls][i]*f[rs][i]);
And(g,-1),And(f[ls],-1),And(f[rs],-1);
for(int i=0;i<limit;i++) add(f[now][i],g[i]);
}
memset(g,0,sizeof(g));
if(s[mid]!='&')
{
Or(f[ls],1),Or(f[rs],1);
for(int i=0;i<limit;i++) add(g[i],1ll*f[ls][i]*f[rs][i]);
Or(g,-1),Or(f[ls],-1),Or(f[rs],-1);
for(int i=0;i<limit;i++) add(f[now][i],g[i]);
}
}
signed main()
{
scanf("%s",s+1),n=strlen(s+1);
m=read();
for(int i=0;i<m;i++)
{
for(int j=0;j<4;j++) a[i].f[j]=read();
S|=(1<<i)*read();
}
limit=1; while(limit<(1<<m)) limit<<=1;
int rt=0;
solve(rt,1,n);
printf("%d\n",f[rt][S]);
return 0;
}
本文来自博客园,作者:樱雪喵,转载请注明原文链接:https://www.cnblogs.com/ying-xue/p/cf582.html