021中国大学生程序设计竞赛(CCPC)- 压力测试赛题解
A.Matrix
挺狗的一道题,从开始冲到最后都没冲出来,都没啥思路。
其实分开考虑每个数的贡献,这个想法也存在过,就是不知道该怎么计算,我们考虑我们单独考虑一个数字\(i(1\leq i\leq n)\)的贡献,其实就是在有一行答案是\(i\)的情况下总的方案数有多少个。同时我们注意到,每个不同的数之间是互不冲突的,因为在一个方案中,我们也是每行求每个数的答案然后累加起来。所以我们只考虑一个数在多少个不同的方案里贡献了答案。首先我们要选一行,其次保证这一行的答案是\(i\),考虑到这一行的其他数必须都比它大,也就是\(C_{n^2-i}^{n-1}\)个,其次考虑这一行\(n\)个数的排列,就是\(n!\),还有就是除了这一行,其他所有数的排列。就是\((n^2-n)!\)也就是说单独一个\(i\)的贡献就是\(n*C_{n^2-i}^{n-1}*n!*(n^2-n)!\)所有数的答案就是\(ans=n*n!*(n^2-n)!\sum_{i=1}^{n}C_{n^2-i}^{n-1}\)数据\(n=5000\),随便都可以过。
//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 998244353
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=5005;
ll jc[N*N];
inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}
inline ll power(ll x,int y)
{
ll ans=1;
while(y)
{
if(y&1) ans=ans*x%P;
y>>=1;
x=x*x%P;
}
return ans%P;
}
inline void prework()
{
jc[0]=1;
rep(i,1,25000000) jc[i]=jc[i-1]*i%P;
}
inline ll C(int n,int m) {return jc[n]*power(jc[m],P-2)%P*power(jc[n-m],P-2)%P;}
int main()
{
//freopen("1.in","r",stdin);
prework();
int get(T);
while(T--)
{
int get(n);
ll ans=n;
ans=ans*jc[n]%P*jc[n*n-n]%P;
ll p=0;
rep(i,1,n) p=(p+C(n*n-i,n-1))%P;
putl(ans*p%P);
}
return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
B.Cypher
关于机关解密的大模拟题....看懂题意谁都会做....
//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 1000000007
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=60;
int p,n,cnt[N],Q;
char id[N],pan[N][3][N],c[N],fan[N],sr[N];
inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}
inline void roll(int id)
{
char c=pan[id][1][0];
rep(i,0,24) pan[id][1][i]=pan[id][1][i+1];
pan[id][1][25]=c;
c=pan[id][2][0];
rep(i,0,24) pan[id][2][i]=pan[id][2][i+1];
pan[id][2][25]=c;
}
inline char solve(char o)
{
int now=1;
while(1)
{
roll(now);
cnt[now]++;
if(cnt[now]%26!=0) break;
now++;
}
int Id=(int)id[o-'A']-'A';
rep(i,1,n)
{
char c=pan[i][1][Id];
rep(j,0,25) if(pan[i][2][j]==c) {Id=j;break;}
}
rep(i,0,25) if(fan[i]-'A'==Id) {Id=i;break;}
fep(i,n,1)
{
char c=pan[i][2][Id];
rep(j,0,25) if(pan[i][1][j]==c) {Id=j;break;}
}
return id[Id];
}
int main()
{
//freopen("1.in","r",stdin);
int get(T);
while(T--)
{
get(p);
rep(i,0,25) id[i]='A'+i;
rep(i,1,p)
{
scanf("%s",sr+1);
swap(id[sr[1]-'A'],id[sr[2]-'A']);
}
get(n);
rep(i,1,n)
{
rep(j,0,25) pan[i][1][j]='A'+j;
}
rep(i,1,n) scanf("%s",pan[i][2]);
memset(cnt,0,sizeof(cnt));
rep(i,1,n)
{
int get(x);
cnt[i]+=x;
rep(j,1,cnt[i])
{
roll(i);
if(cnt[i]%26==0&&i!=n) roll(i+1),cnt[i+1]++;
}
}
scanf("%s",fan);
get(Q);
rep(i,1,Q)
{
scanf("%s",sr+1);
int m=strlen(sr+1);
rep(j,1,m) printf("%c",solve(sr[j]));
puts("");
}
}
return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
C.Vertex Deletion
关于删点的问题,关于这个题,当时也想到了可能要用到树形\(dp\)去解决,也想到了要设\(f[x][0/1]\)来表示清楚当前这个点删不删的方案数,可还是想不太清楚,就弃了。
首先第一个是设状态的问题,我们先考虑我们随便切之后可能会有哪些状态,从根节点往下看,以当前点\(x\)为例,无外乎就三种状态:\(x\)被删掉,\(x\)被保留且只有\(x\)单独的一个点(即\(x\)不与其他点相连),\(x\)被保留且至少还连着其他的一个点。既然有三种状态,那我们设状态时就可以先这样设着\(f[x][0/1/2]\),没有用的时候再剔除也行。那么考虑\(dfs\)的时候,状态如何转移,注意我们定义的状态是以当前点\(x\)为根的子树中,\(x\)处于以上状态时的合法方案数。
那么当\(x\)被删除时,其各个儿子被相互独立那么儿子只能选择连着其儿子来保持方案合法或者选择删除自己。则\(f[x][0]=\prod (f[y][2]+f[y][0])\)。
当\(x\)被保留且单独一个点时,那么儿子必须全部删去,否则就会使得\(x\)与\(y\)相连。即\(f[x][1]=\prod f[y][0]\)
当\(x\)被保留且至少连一个点时,这个方案有点多,我们只要和任意一个儿子相连即可。如果正着求不太行的话,我们考虑容斥,每个儿子有两种选择,要么保留,要么删除,而其中保留还可以选择至少连一个点和删除,这样的话我们直接将所有儿子的两种选择都选上即\(f[x][1]=\prod (f[y][1]+f[y][0]+f[y][2])\)可这样的话发现当所有儿子选择删除时,我们当前的状态就不合法了,但只有这一种情况下才不合法我们直接从总方案中减去即可,而发现这种方案恰好就是\(f[x][1]\)。即\(f[x][1]=\prod (f[y][1]+f[y][0]+f[y][2])-f[x][1]\)。那么最后的答案就是\(f[1][0]+f[1][2]\).
//不等,不问,不犹豫,不回头.
#include<bits/stdc++.h>
#define _ 0
#define ls p<<1
#define db double
#define rs p<<1|1
#define P 998244353
#define ll long long
#define INF 1000000000
#define get(x) x=read()
#define PLI pair<ll,int>
#define PII pair<int,int>
#define ull unsigned long long
#define put(x) printf("%d\n",x)
#define putl(x) printf("%lld\n",x)
#define rep(x,y,z) for(int x=y;x<=z;++x)
#define fep(x,y,z) for(int x=y;x>=z;--x)
#define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y)
using namespace std;
const int N=1e5+10;
ll f[N][3],n;
vector<int>son[N];
inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}
inline void dfs(int x,int fa)
{
f[x][0]=f[x][1]=f[x][2]=1;
for(auto y:son[x])
{
if(y==fa) continue;
dfs(y,x);
f[x][0]=(f[x][0]*(f[y][2]+f[y][0]))%P;
f[x][1]=(f[x][1]*f[y][0])%P;
f[x][2]=(f[x][2]*(f[y][0]+f[y][1]+f[y][2]))%P;
}
f[x][2]=(f[x][2]-f[x][1]+P)%P;
}
int main()
{
//freopen("1.in","r",stdin);
int get(T);
while(T--)
{
get(n);
rep(i,1,n) son[i].clear();
rep(i,1,n-1)
{
int get(x),get(y);
son[x].push_back(y);
son[y].push_back(x);
}
dfs(1,0);
putl((f[1][0]+f[1][2])%P);
}
return (0^_^0);
}
//以吾之血,铸吾最后的亡魂.
D.Lowbit
这个题还是挺好的,发现包括上一次的相乘,这一类题都有一个比较重要的性质,就是某些操作在操作一定次数之后就会出现一些神奇的变化或者不可能成为答案。这个题就是一样的,你会发现这个题每个数在进行一定次数的增加\(lowbit\)之后,等到这个数二进制下只剩一个\(1\)的时候,再加\(lowbit\)就变成乘\(2\)了,而乘\(2\)这个操作我们很容易维护,所以直接写一个线段树,维护一下每个数是不是到达这个状态了即可。