A
题意:
有无限个方块,上面分别是0,1,2.....。若方块i可以被表示为ax+by(x,y>0),则方块为白色,否则为黑色,给出a,b。问黑方块是否有无限个。
分析:
1:若(a,b)=1,由斐蜀等式,则存在an+bm=1(n,m为整数(不保证大于0))。我们只要考虑一个ab区间,若区间内都是白的,那么一定后面都是白色。
我们考虑2|abnm|到2|abnm|+ab-1,这个区间内显然,2|abnm|为ax+by的形式,之后每加一都是加an+bm,而由于从2|abnm|(|nmb|a+|nma|b)开始,累加后an+bm每个数的x‘,y’恒为正。
所以若(a,b)=1,则黑方块一定有限。
2:若(a,b)=k>1,则非k的倍数一定是黑方块,黑方块无穷。
综上,只要求(a,b)就行了。若为1输出Finite,否则为Infinite。
1 #include<stdio.h> 2 #include<iostream> 3 using namespace std; 4 int gcd(int x,int y) 5 { 6 int r=x%y; 7 while (r!=0) 8 { 9 x=y; 10 y=r; 11 r=x%y; 12 } 13 return y; 14 } 15 int main() 16 { 17 int t,k,a,b; 18 scanf("%d",&t); 19 for (k=1;k<=t;k++) 20 { 21 scanf("%d%d",&a,&b); 22 if (gcd(a,b)==1) printf("Finite\n"); 23 else printf("Infinite\n"); 24 } 25 return 0; 26 }
B:
题意:
告诉你你能出a个石头,b个布,c个剪刀。(n=a+b+c)
告诉对面这n回合的出法,你要赢round(n/2)回合(平局和输随便啦,只看赢得场数)
问能否做到,不能输出”NO“,能输出YES,并在下一行输出一种可行的方法(R,P,S一行输出)
分析:
就如果它出剪刀,你就看你有没有石头出,有就出呗。。。因为对着前后的不同剪刀出石头是等效的,贪心就行了。
代码:
1 #include<iostream> 2 #include<cstring> 3 #include<cmath> 4 using namespace std; 5 int main() 6 { 7 double n; 8 int i,k,t,a,b,c,s; 9 char ch; 10 int ans[101]; 11 scanf("%d",&t); 12 for (k=1;k<=t;k++) 13 { 14 scanf("%d",&a); 15 scanf("%d%d%d",&a,&b,&c); 16 getchar(); 17 n=a+b+c; 18 s=0; 19 memset(ans,0,sizeof(ans)); 20 for (i=1;i<=n;i++) 21 { 22 ch=getchar(); 23 if (ch=='R'&&b>0) 24 { 25 b--; 26 s++; 27 ans[i]=2; 28 } 29 if (ch=='P'&&c>0) 30 { 31 c--; 32 s++; 33 ans[i]=3; 34 } 35 if (ch=='S'&&a>0) 36 { 37 a--; 38 s++; 39 ans[i]=1; 40 } 41 } 42 if (s<round(n/2)) printf("NO\n"); 43 else 44 { 45 printf("YES\n"); 46 for (i=1;i<=n;i++) 47 { 48 if (ans[i]==1) printf("R"); 49 else if (ans[i]==2) printf("P"); 50 else if (ans[i]==3) printf("S"); 51 else 52 { 53 if (a>0) 54 { 55 printf("R"); 56 a--; 57 } 58 else if (b>0) 59 { 60 printf("P"); 61 b--; 62 } 63 else 64 { 65 printf("S"); 66 c--; 67 } 68 } 69 } 70 printf("\n"); 71 } 72 } 73 return 0; 74 }
C:
题意:
给你一个打印机打的小写字符组成的字符串,已知打印机会把w打成uu,m打成nn。问这个字符串可能的原串有几种。
分析:
很容易想到DP,因为就一个水的不行的线性DP,如果这个点和前面两个点都是u或者v就是前两项和,不然是前一项。不过有个wa点,出现w和m绝对NG!不过样例给出来了就是了。
代码:
1 #include<iostream> 2 #include<string> 3 #include<cstring> 4 using namespace std; 5 long long dp[100010],flag,l,i; 6 int main() 7 { 8 string str; 9 getline(cin,str); 10 l=str.length(); 11 flag=0; 12 for (i=0;i<l;i++) 13 { 14 if (str[i]=='m'||str[i]=='w') {flag=1;break;} 15 } 16 if (flag==1) cout<<0<<endl; 17 else 18 { 19 dp[0]=1; 20 if ((str[0]=='u'&&str[1]=='u')||(str[0]=='n'&&str[1]=='n')) dp[1]=2; 21 else dp[1]=1; 22 for (i=2;i<l;i++) 23 { 24 if ((str[i]=='u'&&str[i-1]=='u')||(str[i]=='n'&&str[i-1]=='n')) 25 dp[i]=(dp[i-2]+dp[i-1])%1000000007; 26 else dp[i]=dp[i-1]; 27 } 28 cout<<dp[l-1]<<endl; 29 } 30 return 0; 31 }
D:
题意:
有n(0<n<=1e6)个城镇,每个坐标是(Xi,Yi),现在要给每个城镇通上电,对于城镇i,有两种方法,第一种自己发电,代价是Ci,第二种是连到一个有电的城市j,代价是(Ki+Kj)*Dij,Dij表示城镇i和城镇j之间的曼哈顿距离(横坐标差绝对值加纵坐标差绝对值)
分析:
构造一个虚拟的有电城市0,然后自己发电就是连接到0,从而变成了最小生成树,先生成所有边,然后kruskal一下。
代码:
1 #include<iostream> 2 #include<stdio.h> 3 #include<algorithm> 4 #include<cstring> 5 struct node 6 { 7 long long u,v,dis; 8 }; 9 struct node d[2002010]; 10 long long x[2010],y[2010],c[2010],k[2010]; 11 long long ans2[2010][2],ans1[2010],f[2010]; 12 using namespace std; 13 long long find(long long a) 14 { 15 if (a==f[a]) return a; 16 else return find(f[a]); 17 } 18 void combine(long long a,long long b) 19 { 20 f[find(a)]=find(b); 21 } 22 int cmp(struct node a,struct node b) 23 { 24 if (a.dis==b.dis&&a.u==b.u) return a.v<b.v; 25 else if (a.dis==b.dis) return a.u<b.u; 26 else return a.dis<b.dis; 27 } 28 int main() 29 { 30 long long n,i,m,res1,res2,sum,j,u,v; 31 scanf("%I64d",&n); 32 for (i=1;i<=n;i++) 33 { 34 scanf("%I64d%I64d",&x[i],&y[i]); 35 } 36 m=0; 37 for (i=1;i<=n;i++) 38 { 39 scanf("%I64d",&c[i]); 40 m++; 41 d[m].dis=c[i]; 42 d[m].u=0; 43 d[m].v=i; 44 } 45 for (i=1;i<=n;i++) 46 { 47 scanf("%I64d",&k[i]); 48 } 49 for (i=1;i<=n-1;i++) 50 for (j=i+1;j<=n;j++) 51 { 52 m++; 53 d[m].dis=(abs(x[i]-x[j])+abs(y[i]-y[j]))*(k[i]+k[j]); 54 d[m].u=i; 55 d[m].v=j; 56 } 57 sort(d+1,d+m+1,cmp); 58 // for (i=1;i<=m;i++) 59 // { 60 // cout<<d[i].u<<" "<<d[i].v<<" "<<d[i].dis<<endl; 61 // } 62 for (i=0;i<=n;i++) 63 { 64 f[i]=i; 65 } 66 res1=0; 67 res2=0; 68 sum=0; 69 70 for (i=1;i<=m;i++) 71 { 72 u=d[i].u; 73 v=d[i].v; 74 if (find(u)!=find(v)) 75 { 76 sum=sum+d[i].dis; 77 combine(u,v); 78 if (u==0) 79 { 80 res1++; 81 ans1[res1]=v; 82 } 83 else 84 { 85 res2++; 86 ans2[res2][0]=u; 87 ans2[res2][1]=v; 88 } 89 } 90 if (res1+res2==n) break; 91 } 92 printf("%I64d\n",sum); 93 printf("%I64d\n",res1); 94 for (i=1;i<res1;i++) 95 { 96 printf("%I64d ",ans1[i]); 97 } 98 printf("%I64d\n",ans1[res1]); 99 printf("%I64d\n",res2); 100 for (i=1;i<=res2;i++) 101 { 102 printf("%I64d %I64d\n",ans2[i][0],ans2[i][1]); 103 } 104 return 0; 105 } 106
E:
题意:
有个10*10的方格,一个人从(10,1)蛇形(先横向前,再向上一格再横向后,再向上一格)向终点(1,1)前进。
每次前进都会丢一个1-6的骰子(几率相同),决定这次前进几步,如果到重点还有k步,投的大于k就会作废。
当然,不会这么简单,这个方格上存在一些梯子,可以竖着向上走一些长度。当你回合末停在一个梯子下的时候你可以选择向上爬(也可以不爬),下回合将在梯子顶的格子开始。需要注意的是,一个格子顶多有一个向上的梯子,不过可以有很多梯子通往这个格子。另爬梯子上来之后不能接着爬(毕竟回合末才能爬)
现在想尽可能快的走到终点(也就是中间选择爬不爬梯子将取决于那个更快),问总回合数的期望是多少。
分析:
概率DP,正着来的话,梯子的问题很难处理,不过每个格子只有一个向上的梯子,所以很容易想到倒退(概率DP常见手法)
需要注意的有:不是每一步都是除以6,有些点数不可能。
代码:
1 #include<iostream> 2 #include<stdio.h> 3 #include<cstring> 4 #include<string> 5 #include<cmath> 6 using namespace std; 7 double dp[12][12];//[2]; 8 //double dp2[12][12][2]; 9 int a[12][12]; 10 int c[12][12]; 11 int h[101]; 12 int l[101]; 13 int main() 14 { 15 int i,j,k,n,t; 16 double sum; 17 for (i=1;i<=10;i++) 18 for (j=1;j<=10;j++) 19 { 20 cin>>a[i][j]; 21 } 22 for (i=1;i<=10;i++) 23 for (j=1;j<=10;j++) 24 { 25 h[i*10+j-10]=i; 26 if (i%2) l[i*10-10+j]=j; 27 else l[i*10-10+j]=11-j; 28 c[h[i*10+j-10]][l[i*10+j-10]]=i*10+j-10; 29 } 30 //for (i=1;i<=10;i++) 31 //for (j=1;j<=10;j++) 32 //{ 33 // cout<<c[i][j]<<" "; 34 // if (j==10) cout<<endl; 35 //} 36 dp[1][1]=0; 37 for (k=2;k<=100;k++) 38 { 39 sum=0; 40 n=0; 41 for (t=1;t<=6;t++) 42 { 43 if (k>t) 44 { 45 if (a[h[k-t]][l[k-t]]!=0) 46 { 47 if (dp[h[k-t]][l[k-t]]>dp[h[c[h[k-t]-a[h[k-t]][l[k-t]]][l[k-t]]]][l[c[h[k-t]-a[h[k-t]][l[k-t]]][l[k-t]]]]) 48 sum=sum+dp[h[c[h[k-t]-a[h[k-t]][l[k-t]]][l[k-t]]]][l[c[h[k-t]-a[h[k-t]][l[k-t]]][l[k-t]]]]; 49 else sum=sum+dp[h[k-t]][l[k-t]]; 50 } 51 else sum=sum+dp[h[k-t]][l[k-t]]; 52 n++; 53 } 54 else break; 55 } 56 dp[h[k]][l[k]]=(sum+6)/n; 57 } 58 printf("%.10f\n", dp[10][1]); 59 return 0; 60 }
F:
题意:
t(最多100)组询问,每组询问为问区间[l,r],中间有多少组数a,b满足a+b=a^b(0<=l<=r<=1e9)
分析:
乍一看傻眼了,不过经验性的一个处理区间。
ans=solve(r,r)-solve(l-1,r)*2+solve(l,l);
solve(a,b)表示第一个数从1-a,第二个数从1-b
然后是按二进制位嘛,我下意识的想到的思路是这样的,找到两个数二进制数位中最高的那个位,
然后如果两个数这位都是1,那么,可以拆分成3个子情况递归,分别是
1:第一个数那位为1,第二个数那个位为0。
2:第一个数那位为0,第二个数那个位为1。
3:第一个数那位为0,第二个数那个位也为0。
然后我duang duang的敲了个代码,修了下过了样例。。。。。。。的前两个,然后第三个居然跳不出结果。咋回事呢?
我一拍脑袋,这玩意根本不降低多少复杂度,某些情况我可能反而天才的提升了复杂度(不愧是我)
怎么改进呢,很明显递归重复计算了很多相同的子情况,很容易想到记录下来空间换时间。
我意识到,递归的子情况无非是a变成a-(1<<i),min(a,(1<<i)-1),也就是要么是去掉首位,要么是变成111....1,除了1111...1,之外,其他都是取原a的从后往前的若干位。b同理。
所以用dp[i][j][k]表示状态,i是二进制位信息,从30-0,jk分别用0,1表示a,b变成啥状态。就是个结合位运算的dp。
代码:
1 #include<iostream> 2 #include<stdio.h> 3 #include<cstring> 4 using namespace std; 5 long long dp[32][2][2]; 6 long long solve(long n, long m) 7 { 8 long long a,b,c,d; 9 memset(dp,0,sizeof(dp)); 10 dp[31][0][0] = 1; 11 for (int i = 30; i >= 0; --i) 12 { 13 for (a = 0; a <= 1; a++) 14 for (b = 0; b <= 1; b++) 15 for (c = 0; c <= 1; c++) 16 for (d = 0; d <= 1-c; d++) 17 if ((a|| c <= (n >> i & 1)) && (b || d <= (m >> i & 1))) 18 dp[i][a | (c < (n >> i & 1))][b | (d < (m >> i & 1))] += dp[i+1][a][b]; 19 } 20 return dp[0][1][1]; 21 } 22 int main() 23 { 24 long long i,j,t,k,l,r,ans; 25 cin>>t; 26 for (k=1;k<=t;k++) 27 { 28 cin>>l>>r; 29 ans=solve(r+1,r+1)-solve(l,r+1)*2+solve(l,l); 30 cout<<ans<<endl; 31 } 32 return 0; 33 }