题目列表:http://hihocoder.com/contest/msbop2015round2b/problems
第一题:扑克牌。
题意:一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。输出模2^64之后的值。牌的表示方法为XY,其中X为面值,为2、3、4、5、6、7、8、9、T、J、Q、K、A中的一个。Y为花色,为S、H、D、C中的一个。如2S、2H、TD等。
输入:第一行为一个整数T,为数据组数。之后每组数据占一行。这一行首先包含一个整数N,表示给定的牌的张数,接下来N个由空格分隔的字符串,每个字符串长度为2,表示一张牌。每组数据中的扑克牌各不相同。
思路:小数据搜索就能过,大数据(N<=52)需要用动态规划(记忆化搜索)。可以有两种思路。
1、四维dp。dp[a][b][c][d]表示用a个只出现1次的、b个出现2次的、c个出现3次的和d个出现4次的能够放置的方法数。每次枚举当前位放置的是出现几次(1~4)的,然后求和。对于当前放置出现1~4次的转移方程见代码。拿出现3次的来解释:当前位放置的方法数有3c个,这样放置会减少一个出现3次的,增加一个出现两次的,所以剩下的放置数是dp(a,b+1,c-1,d)。但是后者包括下一个位置放置的与当前位相同的情况,所以要减去这种情况。与当前位相同有两种选择,所以是2*dp(a+1,b,c-1,d)。但是一定注意,这样减又减多了,因为减去的部分包括下一位和下下位都相同的情况,而这种在dp(a,b+1,c-1,d)里是不会出现的,所以还得加回来。如此这般,转移方程就写好了。
2、dp[a][b][c][d][pre]表示用a个只出现1次的、b个出现2次的、c个出现3次的和d个出现4次的以及上一位是pre次能够放置的方法数。总体思路与1差不多,这样的好处是不用考虑加多减多。状态转移方程见代码。
注意:题目要求对答案模2^64,那么应该用unsigned long long 来存储(输出是%llu),而且相当于不做任何处理直接加即可,因为如果溢出就相当于模2^64运算。
思路1:
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MOD 1<<64 #define N 54 #define M 13 char ch[5]; int num[M+5],hh[1000]; int T,c,n; unsigned long long dp[M+3][M+3][M+3][M+3]; void init(){ int i; for(i = 2;i<=9;i++) hh['0'+i] = i; hh['T'] = 10; hh['J'] = 11; hh['Q'] = 12; hh['K'] = 13; hh['A'] = 1; memset(dp, -1, sizeof(dp)); dp[0][0][0][0]= 1; } unsigned long long test(int a,int b,int c,int d){ unsigned long long res = 0; if(dp[a][b][c][d] != -1) return dp[a][b][c][d]; if(a > 0) //放置出现一次的 res += a*test(a-1,b,c,d); if(b > 0) //放置出现两次的 res += 2*b*(test(a+1, b-1, c, d) - test(a, b-1, c, d)); if(c > 0) //放置出现三次的 res += 3*c*(test(a, b+1, c-1, d) - 2*(test(a+1,b,c-1,d) - test(a,b,c-1,d))); if(d > 0) //放置出现四次的 res += 4*d*(test(a, b, c+1, d-1) - 3*(test(a,b+1,c,d-1) - 2*(test(a+1,b,c,d-1) - test(a,b,c,d-1)))); return dp[a][b][c][d] = res; } int main(){ init(); scanf("%d",&T); for(c = 1;c<=T;c++){ int i,x,y,z,w; memset(num, 0, sizeof(num)); scanf("%d",&n); for(i = 1;i<=n;i++){ scanf("%s",ch); num[hh[ch[0]]]++; } x = y = z = w = 0; for(i = 1;i<=M;i++){ if(num[i]==1) x++; else if(num[i] == 2) y++; else if(num[i] == 3) z++; else if(num[i] == 4) w++; } printf("Case #%d: %llu\n",c,test(x,y,z,w)); } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MOD 1<<64 #define N 54 #define M 13 char ch[5]; int num[M+5],hh[1000]; int T,c,n; unsigned long long dp[M+3][M+3][M+3][M+3][5]; void init(){ int i; for(i = 2;i<=9;i++) hh['0'+i] = i; hh['T'] = 10; hh['J'] = 11; hh['Q'] = 12; hh['K'] = 13; hh['A'] = 1; memset(dp, -1, sizeof(dp)); for(i = 0;i<5;i++) dp[0][0][0][0][i] = 1; } unsigned long long test(int a,int b,int c,int d,int pre){ unsigned long long res = 0; if(dp[a][b][c][d][pre] != -1) return dp[a][b][c][d][pre]; if(a > 0){ if(pre != 1) res += a*test(a-1,b,c,d,0); else res += (a-1)*test(a-1,b,c,d,0); } if(b > 0){ if(pre != 2) res += 2*b*(test(a+1, b-1, c, d ,1)); else res += 2*(b-1)*(test(a+1, b-1, c, d ,1)); } if(c > 0){ if(pre != 3) res += 3*c*(test(a, b+1, c-1, d ,2)); else res += 3*(c-1)*(test(a, b+1, c-1, d ,2)); } if(d > 0){ if(pre != 4) res += 4*d*(test(a, b, c+1, d-1 ,3)); else res += 4*(d-1)*(test(a, b, c+1, d-1 ,3)); } return dp[a][b][c][d][pre] = res; } int main(){ init(); scanf("%d",&T); for(c = 1;c<=T;c++){ int i,x,y,z,w; memset(num, 0, sizeof(num)); scanf("%d",&n); for(i = 1;i<=n;i++){ scanf("%s",ch); num[hh[ch[0]]]++; } x = y = z = w = 0; for(i = 1;i<=M;i++){ if(num[i]==1) x++; else if(num[i] == 2) y++; else if(num[i] == 3) z++; else if(num[i] == 4) w++; } printf("Case #%d: %llu\n",c,test(x,y,z,w,0)); } return 0; }
第二题:攻城略地。
题意:A、B两国间发生战争。已知A国共有n个城市(编号1, 2, …, n),城市间有一些道路相连。每座城市的防御力为w,直接攻下该城的代价是w。若该城市的相邻城市(有道路连接)中有一个已被占领,则攻下该城市的代价为0。除了占领城市,B国还要摧毁A国的交通系统,因而他们需要破坏至少k条道路。由于道路损毁,攻下所有城市的代价相应会增加。假设B国可以任意选择要摧毁的道路,那么攻下所有城市的最小代价是多少?
输入:第一行一个整数T,表示数据组数,以下是T组数据。每组数据第一行包含3个整数n, m, k。第二行是n个整数,分别表示占领城市1, 2, …, n的代价w。接下来m行每行两个数i, j,表示城市i与城市j间有一条道路。
思路:实际上是贪心的想法。首先考虑原图(不删除任何边),其最小代价是每个连通分支中的最小代价之和。下面考虑删边,如果可以继续删边而不增加连通分支数量,那么最小代价不会增加。直到将原图删成了一棵森林。如果还需要继续删边,那么必然会增加代价,而显然增加的量为没有计入总代价的代价最小点的代价(设为t点)。因为总可以在t所在的连通分支中将t与之前此连通分支内的最小代价点分开。如此删除直到删除符合题意的边为止。
一开始大数据没有过,因为我在读入数据后先排序,然后每找到一个连通分支的最小代价就标记相应位置的数已经选取。实际上这样的话如果原始的图没有边,那么复杂度变成了O(n^2),所以会TLE。实际上在深搜判连通的时候再产生t数组,最后排序扫一遍即可,复杂度O(nlogn)。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define N 1000005 #define INF 0x3fffffff int c,T,n,m,q,w,len; int s[N],t[N],first[N],flag[N],top; struct edge{ int y,next,w; }e[N<<1]; long long res; void add(int x,int y){ e[top].y = y; e[top].next = first[x]; first[x] = top++; } void dfs(int x){ int i; flag[x] = 1; if(w>s[x]){ //此时再生成t数组 if(w != INF) t[len++] = w; w = s[x]; }else t[len++] = s[x]; for(i = first[x];i!=-1;i=e[i].next) if(-1 == flag[e[i].y]) dfs(e[i].y); } int main(){ scanf("%d",&T); for(c = 1;c<=T;c++){ int i,j,a,b,con=0; res = len = 0; memset(flag, -1, sizeof(flag)); memset(first, -1, sizeof(first)); top = 0; scanf("%d %d %d",&n,&m,&q); for(i = 1;i<=n;i++) scanf("%d",&s[i]); for(i = 1;i<=m;i++){ scanf("%d %d",&a,&b); add(a,b); add(b,a); } for(i = 1,con=0;i<=n;i++) if(flag[i] == -1){ w = INF; dfs(i); res += w; //w求得为每个连通分支的最小代价 con ++; //连通分支数 } sort(t,t+len); j = n-con-(m-q); //j是将原图删成森林之后还需要删除的边数 for(i = 0;j>0;j--,i++) res += t[i]; printf("Case #%d: %lld\n",c,res); } return 0; }
题意:某社交网站中有N个用户,用户和用户之间有亲密度。亲密度非负,若大于零表示这两个用户之间是好友关系。小冰会不停地更新用户之间的亲密度,同时,小冰也有可能会改变对一个用户性别的判断。小冰想知道这个社交网络的八卦度是多少。八卦度的定义是社交网络中所有异性好友之间的亲密度之和。对于每一个询问,输出一行包含询问的八卦度。
输入:第一行一个整数T,表示数据组数。接下来是T组数据,每组数据的格式如下:第一行是三个整数N, M, Q,分别表示用户数、初始的好友对数、操作数。
第二行是N个空格隔开的数,第i个数表示i号用户的性别,用0或1表示。
接下来的M行,每行三个数x, y, z,代表初始状态用户x和用户y之间的亲密度是z。除此之外的用户之间的亲密度初始为0。
接下来是Q行,每行是以下三种操作中的一种:
1. “1 x”:改变用户x的性别
2. “2 x y z”:改变用户x与用户y之间的亲密度为z
3. “3”:询问八卦度
思路:最原始的思路就是模拟,但着实没想到模拟就能过大数据。因为大数据范围是:1 ≤ N, M, Q ≤ 100000,那么如果最后每两个人之间都有亲密度,那么2*100000^2这个量级的边没法存。看了下别人的AC代码也基本都是模拟过的。其实这题的数据可能就是询问八卦度(操作3)的数量比较多,所以应该维护结果的值而不是每次询问现计算。初次之外没有什么可注意的。
#include <cstdio> #include <cstring> #include <algorithm> #define N 100005 int c,T,n,m,q; int s[N],first[N],top; struct edge{ int y,next,w; }e[N<<5]; long long res; void add(int x,int y,int w){ e[top].y = y; e[top].w = w; e[top].next = first[x]; first[x] = top++; } void update(int x){ int i,y; for(i = first[x];i!=-1;i=e[i].next){ y = e[i].y; if(s[x] == s[y]) res -= e[i].w; else res += e[i].w; } } int change(int x,int y,int w){ int i,tmp = -1; for(i = first[x];i!=-1;i=e[i].next){ if(e[i].y == y){ tmp = e[i].w; e[i].w = w; return tmp; } } add(x,y,w); return 0; } int main(){ scanf("%d",&T); for(c = 1;c<=T;c++){ int i,j,a,b,w,op; res = 0; memset(first, -1, sizeof(first)); top = 0; scanf("%d %d %d",&n,&m,&q); for(i = 1;i<=n;i++) scanf("%d",&s[i]); for(i = 1;i<=m;i++){ scanf("%d %d %d",&a,&b,&w); add(a,b,w); add(b,a,w); if(s[a] != s[b]) res += w; } printf("Case #%d:\n",c); while(q--){ scanf("%d",&op); if(op == 1){ scanf("%d",&a); s[a] = 1-s[a]; update(a); }else if(op == 2){ scanf("%d %d %d",&a,&b,&w); j = change(a,b,w); change(b,a,w); if(s[a] != s[b]){ res -= j; res += w; } }else printf("%lld\n",res); } } return 0; }