Day1
中纪集训第一天
T1 水叮当的舞步
Description
水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。
为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~
地毯上的格子有N行N列,每个格子用一个0~5之间的数字代表它的颜色。
水叮当可以随意选择一个0~5之间的颜色,然后轻轻地跳动一步,地毯左上角的格子所在的联通块里的所有格子就会变成她选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。
由于水叮当是施展轻功来跳舞的,为了不消耗过多的真气,她想知道最少要多少步才能把所有格子的颜色变成一样的。
Input
每个测试点包含多组数据。
每组数据的第一行是一个整数N,表示地摊上的格子有N行N列。
接下来一个N*N的矩阵,矩阵中的每个数都在0~5之间,描述了每个格子的颜色。
N=0代表输入的结束。
Output
对于每组数据,输出一个整数,表示最少步数。
Sample Input
2
0 0
0 0
3
0 1 2
1 1 2
2 2 1
0
Sample Output
0
3
Data Constraint
对于30%的数据,N<=5
对于50%的数据,N<=6
对于70%的数据,N<=7
对于100%的数据,N<=8,每个测试点不多于20组数据。
看见就觉得眼熟
这就是裸的IDA*
然后就毫不犹豫地打出了如下代码
#include<bits/stdc++.h> using namespace std; int n,r[10][10],ans,moi[5]={0,1,-1,0,0},moj[5]={0,0,0,1,-1}; int img() { int colo[10],ret=0; for(int i=0;i<=5;i++) colo[i]=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) colo[r[i][j]]=1; for(int i=0;i<=5;i++) if(colo[i]) ret++; return ret-1; } bool check() { for(int i=1;i<=n;i++) { if(r[i][1]!=r[i-1][1]&&i!=1) return 0; for(int j=2;j<=n;j++) if(r[i][j]!=r[i][j-1]) return 0; } return 1; } void change(int i,int j,int col) { int co=r[i][j]; r[i][j]=col; for(int k=1;k<=4;k++) { int wi=i+moi[k],wj=j+moj[k]; if(r[wi][wj]==co&&wi>0&&wi<=n&&wj>0&&wj<=n) change(wi,wj,col); } } bool sol(int k,int s) { if(k+img()>s) return 0; if(check()) return 1; int f[10][10],git[10],vi[10][10]; queue<int> q1,q2; memset(git,0,sizeof(git)); memset(vi,0,sizeof(vi)); vi[1][1]=1; q1.push(1);q2.push(1); while(!q1.empty()) { int wi=q1.front(),wj=q2.front(); q1.pop();q2.pop(); for(int i=1;i<=4;i++) { int ki=wi+moi[i],kj=wj+moj[i]; if(vi[ki][kj]||ki<=0||kj<=0||ki>n||kj>n) continue; if(r[ki][kj]==r[wi][wj]) { vi[ki][kj]=1; q1.push(ki); q2.push(kj); } else git[r[ki][kj]]=1; } } memcpy(f,r,sizeof(r)); for(int i=0;i<=5;i++) { if(!git[i]) continue; change(1,1,i); if(sol(k+1,s)) return 1; memcpy(r,f,sizeof(f)); } return 0; } int main() { while(cin>>n) { if(n==0) break; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>r[i][j]; for(int i=0;1;i++) if(sol(0,i)) { cout<<i<<endl; break; } } return 0; }
然而我把正解直接打成了70分
稍微改了一下就过了
#include<bits/stdc++.h> using namespace std; int n,r[10][10],ans,moi[5]={0,1,-1,0,0},moj[5]={0,0,0,1,-1}; int img() { int colo[10],ret=0; for(int i=0;i<=5;i++) colo[i]=0; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) colo[r[i][j]]=1; for(int i=0;i<=5;i++) if(colo[i]) ret++; return ret-1; } void read(int &x) { int f=1;x=0;char s=getchar(); while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();} while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();} x*=f; } bool check() { for(int i=1;i<=n;i++) { if(r[i][1]!=r[i-1][1]&&i!=1) return 0; for(int j=2;j<=n;j++) if(r[i][j]!=r[i][j-1]) return 0; } return 1; } void change(int i,int j,int col) { int co=r[i][j]; r[i][j]=col; for(int k=1;k<=4;k++) { int wi=i+moi[k],wj=j+moj[k]; if(r[wi][wj]==co&&wi>0&&wi<=n&&wj>0&&wj<=n) change(wi,wj,col); } } bool sol(int k,int s) { if(k+img()>s) return 0; if(check()) return 1; int f[10][10],git[10],vi[10][10]; memset(git,0,sizeof(git)); memset(vi,0,sizeof(vi)); vi[1][1]=1; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(vi[i][j-1]||vi[i-1][j]) { if(r[i][j]==r[1][1]) vi[i][j]=1; else git[r[i][j]]=1; } memcpy(f,r,sizeof(r)); for(int i=0;i<=5;i++) { if(!git[i]) continue; change(1,1,i); if(sol(k+1,s)) return 1; memcpy(r,f,sizeof(f)); } return 0; } int main() { while(cin>>n) { if(n==0) break; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) read(r[i][j]); for(int i=0;1;i++) if(sol(0,i)) { cout<<i<<endl; break; } } return 0; }
T2 Vani和Cl2捉迷藏
Description
vani和cl2在一片树林里捉迷藏……
这片树林里有N座房子,M条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子A沿着路走下去能够到达B,那么在A和B里的人是能够相互望见的。
现在cl2要在这N座房子里选择K座作为藏身点,同时vani也专挑cl2作为藏身点的房子进去寻找,为了避免被vani看见,cl2要求这K个藏身点的任意两个之间都没有路径相连。
为了让vani更难找到自己,cl2想知道最多能选出多少个藏身点?
Input
第一行两个整数N,M。
接下来M行每行两个整数x、y,表示一条从x到y的有向道路。
Output
一个整数K,表示最多能选取的藏身点个数。
Sample Input
4 4
1 2
3 2
3 4
4 2
Sample Output
2
Data Constraint
对于20% 的数据,N≤10,M<=20。
对于60% 的数据, N≤100,M<=1000。
对于100% 的数据,N≤200,M<=30000,1<=x,y<=N。
开始看见N的范围,就打了Floyd判断每个点的联通情况
然后就不知道怎么办了
想了一下准备暴搜,直接从联通个数大的开始搜,然后标记,回溯。
于是就用Floyd+暴搜打了35的暴力
#include<bits/stdc++.h> using namespace std; int n,m,ra,rb,s[10001][2],cnt[201],sk[201],ans; bitset<201> git[201],r; bool comp(int a,int b) { return cnt[a]>cnt[b]; } void sol(int i,int k) { if(k>ans) ans=k; bitset<201>f; for(int i=1;i<=n;i++) f[i]=r[i]; int tot=0; for(int i=1;i<=n;i++) if(!f[i]) tot++; if(tot+k<=ans) return; for(int j=i;j<=n;j++) { if(r[sk[j]]) continue; r|=git[sk[j]]; sol(j+1,k+1); for(int i=1;i<=n;i++) r[i]=f[i]; } } int main() { cin>>n>>m; for(int i=1;i<=n;i++) git[i][i]=1,sk[i]=i; for(int i=1;i<=m;i++) { cin>>ra>>rb; git[ra][rb]=1; s[i][0]=rb; s[i][1]=ra; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) if(git[i][k]&&git[k][j]) git[i][j]=1; for(int i=1;i<=m;i++) git[s[i][0]][s[i][1]]=1; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(git[i][j]) cnt[i]++; sort(sk+1,sk+n+1,comp); sol(1,0); cout<<ans<<endl; return 0; }
正解:
此题就是寻找最小路径覆盖
因为每条链上只能选一个点
所以找出几条链可以覆盖全图就可以了
由于这个又可以转化为二分图最大匹配
于是Floyd传递闭包+二分图匹配就可以A了
第一次接触二分图匹配
用到了 _匈牙利算法_
其实匈牙利跟网络流有点像,都是寻找增广路
代码:
#include<bits/stdc++.h> using namespace std; int n,m,f[201][201],ant,ra,rb,g[201],v[201]; bool fid(int x) { for(int i=1;i<=n;i++) { if(f[x][i]) if(!v[i]) { v[i]=1; if(!g[i]||fid(g[i])) { g[i]=x; return 1; } } } return 0; } int main() { cin>>n>>m; for(int i=1;i<=m;i++) { cin>>ra>>rb; f[ra][rb]=1; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) for(int k=1;k<=n;k++) if(f[i][k]&&f[k][j]) { f[i][j]=1; break; } for(int i=1;i<=n;i++) { memset(v,0,sizeof(v)); if(fid(i)) ant++; } cout<<n-ant<<endl; return 0; }
T3 粉刷匠
Description
赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有N根排列成一直线的石柱,从1到N标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有K桶颜色各不相同的油漆,第i桶油漆恰好可以粉刷Ci根石柱,并且,C1+C2+C3…CK=N(即粉刷N根石柱正好用完所有的油漆)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。
喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?
为了让赫克托尽快开始粉刷,请你尽快告诉他答案。
Input
第一行一个正整数T,表示测试数据组数
对于每一组测试数据数据:
第1行:一个正整数K
第2行:K个正整数,表示第i桶油漆可以粉刷的石柱个数,Ci。
Output
对于每组输入数据,输出一行一个整数,表示粉刷的方案数mod 1000000007。
Sample Input
3
3
1 2 3
5
2 2 2 2 2
10
1 1 2 2 3 3 4 4 5 5
Sample Output
10
39480
85937576
Data Constraint
30% N≤10, T≤5
50% N≤15, T≤5
80% K≤15,Ci≤5,T≤500
100% K≤15,Ci≤6,T≤2000
考的时候发现考数学
应该是组合数学,想一个个插入
但是不知道怎么判重
觉得可能是dp
但是写不出来
然后认清现实抵抗无效
#include<bits/stdc++.h> using namespace std; long long t,n,ans,mod=1e9+7,tot,s[21],mx; int main() { cin>>t; srand(time(0)); while(t--) { cin>>n; ans=1,mx=0,tot=0; for(int i=1;i<=n;i++) cin>>s[i],mx=max(mx,s[i]); for(int i=1;i<=mx;i++) { for(int j=1;j<=n;j++) if(s[j]>=i) { ans*=(tot+1)-(i-1)*2; ans%=mod; tot++; } } ans=rand()+rand()+rand(); ans%=mod; cout<<ans<<endl; } return 0; }
正解:
数论+dp
设F[i][j]表示涂到第i种颜色,有j对相邻的柱子颜色相同。
记涂到第i种颜色之后一共有S根柱子。
对于i+1这种颜色,能粉刷A次。
设把这种颜色分成k块,插进序列里面。
插进的位置中,有l个位置刚好插进在j对颜色相同的柱子中间(l<=j 且 l<=k)。
新状态的j:原有的j,新增的A-k,减少的l。
状态转移:
1 把颜色分成k块的方案数:C(A-1, k-1)
2 把块插进l个位置的方案数:C(j, l)
3 剩余块的处理方式:C(S+1-j, k-l)
转移方程如下:
F[i+1][j+A-k-l] += F[i][j] * C(j, l) * C(A-1, k-1) * C(S+1-j, k-l)
同时可以先预处理掉C的排列组合
代码
#include<bits/stdc++.h> #define int long long using namespace std; int n,t,x,c[101][101],f[20][101],mod=1e9+7; signed main() { c[0][0]=1; for(int i=1;i<=100;i++) { c[i][0]=1; for(int j=1;j<=i;j++) { c[i][j]=c[i-1][j]+c[i-1][j-1]; c[i][j]%=mod; } } cin>>t; while(t--) { cin>>n; f[0][0]=1; int sum=0; for(int i=1;i<=n;i++) { cin>>x; for(int j=0;j<=sum+x+1;j++) f[i][j]=0; for(int j=0;j<=sum;j++) for(int k=0;k<=min(j,x);k++) for(int l=0;l<=min(x-k,sum+1-j);l++) { if(!(k+l)) continue; int ans=f[i-1][j],y=j+x-2*k-l; ans*=c[x-1][k+l-1];ans%=mod; ans*=c[j][k];ans%=mod; ans*=c[sum+1-j][l];ans%=mod; f[i][y]+=ans;f[i][y]%=mod; } sum+=x; } cout<<f[n][0]<<endl; } }