您的位置:首页 > 编程语言

2015微软编程之美挑战赛初赛第2场

2015-04-26 14:42 483 查看
题目列表: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;
}


思路2:

#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
,t
,first
,flag
,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
,first
,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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: