HITtrainning20140417题解
题目列表:
ID | Origin | Title | ||
---|---|---|---|---|
10 / 15 | Problem A | FZU 2152 | 文件系统 | |
0 / 16 | Problem B | FZU 2153 | A simple geometric problems | |
9 / 17 | Problem C | FZU 2154 | YesOrNo | |
4 / 18 | Problem D | FZU 2155 | 盟国 | |
10 / 20 | Problem E | FZU 2156 | Climb Stairs | |
5 / 11 | Problem F | FZU 2157 | ProgramCaicai's Trees | |
1 / 7 | Problem G | FZU 2158 | 数字密码 | |
6 / 34 | Problem H | FZU 2159 | WuYou |
A - 文件系统
比较复杂的模拟,用bool in[i][j]记录i号人是否在j组里,注意细节就能撸出来,我交了好多次,简直逗
#include<iostream> #include <cstdio> #include <cstring> //#include <cstdlib> #define RE freopen("in.txt","r",stdin); using namespace std; int farm(); int main() { int t,T; //RE cin>>T; for(t=0;t<T;t++) { farm(); } return 0; } int farm() { int n,i,j,m,x; bool in[100][101]; string username[100]; string groupname[100]; int quanxian[100]; string ftouser[100]; int ftogroup[100]; int groupn=0; string s; memset(in,0,sizeof(in)); cin>>n; for(i=0;i<n;i++) { cin>>username[i]; cin>>x; for(j=0;j<x;j++) { cin>>s; bool ok=false; for(int k=0;k<groupn;k++) if(groupname[k]==s) { ok=true; in[i][k]=true; break; } if (!ok) { groupname[groupn]=s; in[i][groupn]=true; groupn++; } } } cin>>m; for(i=0;i<m;i++) { cin>>s; cin>>quanxian[i]; cin>>ftouser[i]; cin>>s; ftogroup[i]=100; for(int k=0;k<groupn;k++) if(groupname[k]==s) { ftogroup[i]=k; break; } //cout<<i<<'.'<<ftogroup[i]<<endl; }/* for(i=0;i<n;i++) { for(j=0;j<groupn;j++) cout<<in[i][j]<<','; cout<<endl; }*/ for(i=0;i<n;i++) { j=0; if (ftouser[j]==username[i]) cout<<(int)(quanxian[j]/100); else {if(in[i][ftogroup[j]]) cout<<((int)(quanxian[j]/10))%10; else cout<<(quanxian[j]%10);} for(j=1;j<m;j++) { cout<<' '; if (ftouser[j]==username[i]) cout<<(int)(quanxian[j]/100); else {if(in[i][ftogroup[j]]) cout<<((int)(quanxian[j]/10))%10; else cout<<(quanxian[j]%10);} } cout<<endl; } return 0; }
B - A simple geometric problems
不会,好像不仅要凸包,虽然凸包我也不会。不过也没人做出来我就不管了……
C - YesOrNo
试一下发现不管换多少次,都可以换一次达到。就二重循环,if(s1[j]!=s2[(i+j)%length])break;直接对比就行。我还怕它出奇怪的数据,例如所有字符都一样,只有一个字符不一样,这样要对比好久……可是好像没有这样的数据耶,不管了。
#include<iostream> #include <cstdio> #include <cstring> //#include <cstdlib> #define MAXN 500 #define RE freopen("inC.txt","r",stdin); using namespace std; char s1[500000]; char s2[500000]; char a1[256]; char a2[256]; int farm(); void init(); int main() { //RE while(scanf("%s",s1)!=EOF) { farm(); } return 0; } int farm() { int i,j,st; init(); scanf("%s",s2); if(strlen(s1)!=strlen(s2)){cout<<"No"<<endl;return 0;} int length=strlen(s1); for(i=0;i<length;i++) { } st=0;j=0; for(i=0;i<length;i++) { for(j=0;j<length;j++) { if(s1[j]!=s2[(i+j)%length])break; } if(j==length){cout<<"Yes"<<endl;return 0;} } cout<<"No"<<endl; return 0; } void init() { memset(a1,0,sizeof(a1)); memset(a2,0,sizeof(a2)); }
D - 盟国
竟然是要带删除操作的并查集,当时没做出来。并查集用两个数组来,a[i]存一般的并查集的东西,b[i]表示国家i用的并查集物体存在a[b[i]],这样的话,删除就是把b[i]指向一个新的a[j],新创建一个a[j]来存b[i]的并查集物体,而以前用的那个就不管了,继续留在并查集里……这样就相当于删掉啦!最后统计一下同盟数就好。
#include<iostream> #include <cstdio> #include <cstring> //#include <cstdlib> #define RE freopen("inD.txt","r",stdin); using namespace std; struct biu{ int n; int father; }; int N,M; biu a[1200000]; int b[100000]; bool g[1200000]; int w; int ans; int t; int farm(); void init(); int getfather(int x) { int y; if(a[x].father!=x) y=getfather(a[x].father); else return x; a[x].father=y; return y; } int meng(int x,int y) { int fx=getfather(b[x]); int fy=getfather(b[y]); if(fx==fy) return 0; if(a[fx].n>a[fy].n) { a[fy].father=fx; a[fy].n+=a[fx].n; } else { a[fx].father=fy; a[fx].n+=a[fy].n; } //ans--; return 0; } int out(int x) { b[x]=w; a[w].n=1; a[w].father=w; w++; //ans++; return 0; } int main() { t=0; //RE while(true) { scanf("%d%d",&N,&M); if(N==0) return 0; farm(); } return 0; } int farm() { int i,j,x,y; char c; init(); for(i=0;i<M;i++) { do{scanf("%c",&c);}while(c!='M' && c!='S'); if(c=='M') { scanf("%d%d",&x,&y); //cout<<x<<','<<y<<endl; meng(x,y); } else { scanf("%d",&x); //cout<<x<<endl; out(x); } } for(i=0;i<N;i++) { g[getfather(b[i])]=true; } ans=0; for(i=0;i<w;i++) if(g[i]) ans++; cout<<"Case #"<<++t<<": "<<ans<<endl; return 0; } void init() { int i; for(i=0;i<N;i++) { b[i]=i; a[i].father=i; a[i].n=1; } w=N; //ans=N; memset(g,0,sizeof(g)); }
E - Climb Stairs
意思是这个人一次能走X级或Y级台阶,要统计经过A台阶和B台阶到达N台阶的方法数。简单动规,我是先统计到A、B中较低的那个的方法数,然后把之前的方法数全清零!然后再统计到A、B中较高的那个方法数,再把之前的清零,这样就只剩经过A和B的方法了,然后再撸到N级就行了。
#include<iostream> #include <cstdio> //#include <cstring> //#include <cstdlib> #define MAXN 500 #define RE freopen("inE.txt","r",stdin); using namespace std; int N, X, Y, A, B; int farm(); int main() { //RE while(cin>>N>>X>>Y>>A>>B) { farm(); } return 0; } int farm() { int i,j,k; int dp[10000]={0}; dp[0]=1; for(i=1;i<=A && i<=B;i++) { if (i>=X) dp[i]=(dp[i]+dp[i-X])%1000000007; if (i>=Y) dp[i]=(dp[i]+dp[i-Y])%1000000007; } j=i; for(i=0;i<j-1;i++) dp[i]=0; for(i=j-1;i<=A || i<=B;i++) { if (i>=X) dp[i]=(dp[i]+dp[i-X])%1000000007; if (i>=Y) dp[i]=(dp[i]+dp[i-Y])%1000000007; } k=i; for(i=j-1;i<k-1;i++) dp[i]=0; for(i=k-1;i<=N;i++) { if (i>=X) dp[i]=(dp[i]+dp[i-X])%1000000007; if (i>=Y) dp[i]=(dp[i]+dp[i-Y])%1000000007; } cout<<dp[N]<<endl; return 0; }
F - ProgramCaicai's Trees
意思是有一棵树,要你选择每个节点是0还是1,每个节点是0或者是1各自有不同的分数,还有各个边的两边是00、01、10、11各有不同的分数。树形DP!其实我以前从来没做过树形DP,当时编了好久还没撸出来。原来是要dfs回溯时DP。好像可以先多叉转二叉再做,不过我是直接像图一样做了…具体看代码。
#include<iostream> #include <cstdio> #include <cstring> //#include <cstdlib> //#include<queue> #define RE freopen("inF.txt","r",stdin); using namespace std; struct bian { int fr,to; int b[2][2]; int next; }; int a[2][200000]; int firstbian[200000]; bian b[400000]; int next[400000]; int bn; bool walked[200000]; int dp[2][200000]; int N; int farm(); void init(); int dfs(int now) { int nb=firstbian[now]; walked[now]=true; //cout<<'('<<now<<','<<choose<<')'; while(nb!=-1) { if(!walked[b[nb].to]) { //cout<<"to "<<b[nb].to<<' '; dfs(b[nb].to); dp[0][now]+=min(dp[0][b[nb].to]+b[nb].b[0][0] , dp[1][b[nb].to]+b[nb].b[0][1]); dp[1][now]+=min(dp[0][b[nb].to]+b[nb].b[1][0] , dp[1][b[nb].to]+b[nb].b[1][1]); } nb=next[nb]; } dp[0][now]+=a[0][now]; dp[1][now]+=a[1][now]; //cout<<now<<','<<choose<<' '<<sum<<'+'<<a[choose][now]<<endl; return 0; } int main() { int t,T; //RE scanf("%d",&T); for(t=0;t<T;t++) { farm(); } return 0; } int farm() { int i,j,P,Q,A,B,C,D; scanf("%d",&N); init(); for(i=0;i<N;i++) { scanf("%d",&a[0][i]); } for(i=0;i<N;i++) { scanf("%d",&a[1][i]); } bn=0; for(i=0;i<N-1;i++) { scanf("%d%d%d%d%d%d",&P,&Q,&A,&B,&C,&D); P--;Q--; b[bn].fr=P; b[bn].to=Q; b[bn].b[0][0]=A; b[bn].b[0][1]=B; b[bn].b[1][0]=C; b[bn].b[1][1]=D; next[bn]=firstbian[b[bn].fr]; firstbian[b[bn].fr]=bn; bn++; b[bn].fr=Q; b[bn].to=P; b[bn].b[0][0]=A; b[bn].b[0][1]=C; b[bn].b[1][0]=B; b[bn].b[1][1]=D; next[bn]=firstbian[b[bn].fr]; firstbian[b[bn].fr]=bn; bn++; } //walked[0]=true; dfs(0); printf("%d\n",min(dp[0][0],dp[1][0])); return 0; } void init() { memset(walked,0,sizeof(walked)); memset(firstbian,-1,sizeof(firstbian)); memset(dp,0,sizeof(dp)); }
G - 数字密码
完全不会!看了woshilalala神犇的代码才会的,超碉。
动规,dp[T],T是八进制数t6t5t4t3t2t1t0,dp[T]代表当第0个人的字符串有t0位在密码里,第1个人的有t1位在密码里……第i个人有ti位在密码里时,密码的最小位数。
dp[0]=0,从dp[0]推到dp[end],end为(T|ti=第i个人字符串长度),dp[end]就是答案。
dp[0]=0,其他是INF,i从0循环到end-1,d[k]=min(d[k],d[i]+1),k是从i的状态增加一个数字能达到的状态,0~9每个数字都要试一遍。
关键部分:
inline int gank(int x,int y) { int next=0,i,t; for(i=0;i<N;i++) { t=x%8; x=x>>3; if(t<len[i] && s[i][t]==y) t++; next+=t<<q[i]; } return next; }
//x是当前状态,y是试的数字(0~9之一),q[i]是3*i。返回的next就是能达到的下一个状态。
举例说明容易理解,例如样例:
3个人,字符串分别是:
123 14 21
dp[0]=0,从0状态开始,试0,发现“0”字符串完全不得力,得到的next还是0,d[0]=min(d[0],d[0]+1),故不做改变。
试1,发现“1”字符串让第一第二个人的字符串得力了,于是得到的next为011(八进制,下同),dp[011]=min(d[011],d[0]+1),由于d[011]初始值为INF,所以dp[011]变为d[0]+1=1。意思是:在011状态(第三个人的字符串还没有一个字符在密码里,第二个人和第一个人的字符都有1个在密码里)下,密码的长度为1。
后面循环到dp[011]的时候,试2的时候,将2加到各字符串中已经确认在密码里的位数之后的那一位,看看是否相同,例如第一个人的字符串,已经确认了一位,将2与第二位对比,发现相同,这个字符串的已经确认的位数就加1。然后看第二个人的字符串,已经确认了一位,将2与第二位对比,发现不同,则已经确认的位数不变,第三个人的字符串同理。因为第一个人的字符串第二个数是2,其他的都没有2,所以得到的next是012,dp[012]=dp[011]+1=2。 这样一直撸下去就能得到答案啦,太碉啦!
#include<iostream> #include <cstdio> #include <cstring> #include<cmath> //#include <cstdlib> #define RE freopen("inG.txt","r",stdin); using namespace std; string s[7]; int q[7]; int d[2097152];//8^7 int len[7]; int N; int farm(); void init(); int main() { int t,T; //RE q[0]=0; for(t=1;t<7;t++) q[t]=q[t-1]+3; cin>>T; for(t=0;t<T;t++) { farm(); } return 0; } inline int gank(int x,int y) { int next=0,i,t; for(i=0;i<N;i++) { t=x%8; x=x>>3; if(t<len[i] && s[i][t]==y) t++; next+=t<<q[i]; } return next; } int farm() { int i,j,k; int tot=0; cin>>N; init(); for(i=0;i<N;i++) { cin>>s[i]; len[i]=s[i].length(); tot+=(len[i]<<q[i]); } d[0]=0; for(i=0;i<tot;i++) { if(d[i]<0x7f) { for(j='0';j<='9';j++) { k=gank(i,j); d[k]=min(d[k],d[i]+1); } } } cout<<d[tot]<<endl; return 0; } void init() { memset(d,0x7f,sizeof(d)); }
H - WuYou
从左到右一位一位看,记住看过的数是等于还是小于,大于就直接-1,小于了一下的话就一直小于了,这样遇见?就能填9。如果是左边都等于的时候遇到问号,有时需要填与B相等的,有时需要填比B小1的,我是深搜的,先搜相等的,后面的位都弄好了成功了就找到了最大的A,不行再搜减一的。
#include<iostream> #include <cstdio> #include <cstring> //#include <cstdlib> #define RE freopen("inH.txt","r",stdin); #define EQUAL 1 #define LITTLE 2 using namespace std; char A[11000],B[11000]; int length; int farm(); int main() { int t,T; //RE cin>>T; for(t=0;t<T;t++) { farm(); } return 0; } int dfs(int now,int EQ) { int x=0; if(now>=length) { if (EQ==LITTLE)return 1; else return 0; } if(A[now]=='?') { if(EQ==LITTLE) { A[now]='9'; x=dfs(now+1,EQ); if (x==1) return 1; A[now]='?'; } else { A[now]=B[now]; if(!(now==0 && A[now]=='0')) x=dfs(now+1,EQ); if(x==1) return 1; if(B[now]!='0') { A[now]=B[now]-1; if(!(now==0 && A[now]=='0')) x=dfs(now+1,LITTLE); if(x==1) return 1; } A[now]='?'; } } else { if(EQ==EQUAL && A[now]>B[now]) return 0; if(EQ==EQUAL && A[now]<B[now]) x=dfs(now+1,LITTLE); else x=dfs(now+1,EQ); } return x; } int farm() { int N,i,j; scanf("%s",A); scanf("%s",B); bool ok=false; length=strlen(A); j=dfs(0,EQUAL); if(j==1) printf("%s",A); else printf("-1"); printf("\n"); return 0; }