【编程之美】初赛2015 待填坑

4 篇文章 0 订阅

初赛第一场

题目1 : 彩色的树

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

给定一棵n个节点的树,节点编号为1, 2, …, n。树中有n - 1条边,任意两个节点间恰好有一条路径。这是一棵彩色的树,每个节点恰好可以染一种颜色。初始时,所有节点的颜色都为0。现在需要实现两种操作:

1. 改变节点x的颜色为y;

2. 询问整棵树被划分成了多少棵颜色相同的子树。即每棵子树内的节点颜色都相同,而相邻子树的颜色不同。

输入

第一行一个整数T,表示数据组数,以下是T组数据。

每组数据第一行是n,表示树的节点个数。接下来n - 1行每行两个数i和j,表示节点i和j间有一条边。接下来是一个数q,表示操作数。之后q行,每行表示以下两种操作之一:

1. 若为"1",则询问划分的子树个数。

2. 若为"2 x y",则将节点x的颜色改为y。

输出

每组数据的第一行为"Case #X:",X为测试数据编号,从1开始。

接下来的每一行,对于每一个询问,输出一个整数,为划分成的子树个数。

数据范围

1 ≤ T ≤ 20

0 ≤ y ≤ 100000

小数据

1 ≤ n, q ≤ 5000

大数据

1 ≤ n, q ≤ 100000

样例输入

2
3
1 2
2 3
3
1
2 2 1
1
5
1 2
2 3
2 4
2 5
4
1
2 2 1
2 3 2
1

样例输出

Case #1:
1
3
Case #2:
1
5

解答 :  

对每个节点,维护一个map<int color, int cnt>,记录他的子节点中颜色为color的个数。
然后就好办了,每次修改一个节点,查看 1 它的子节点是否会和自己发生合并/分裂 2 父节点是否会和自己及兄弟节点发生合并/分裂。更新子数个数。

题目2 : 建造金字塔

时间限制: 4000ms
单点时限: 2000ms
内存限制: 256MB

描述

在二次元中,金字塔是一个底边在x轴上的等腰直角三角形。

你是二次元世界的一个建筑承包商。现在有N个建造订单,每个订单有一个收益w,即建造此金字塔可获得w的收益。对每个订单可以选择建造或不建造。

建造一个金字塔的成本是金字塔的面积,如果两个或多个金字塔有重叠面积,则建造这些金字塔时重叠部份仅需建造一次。

建造一组金字塔的总利润是收益总和扣除成本。现给出这些订单,请求出最大利润。

输入

输入数据第一行为一个整数T,表示数据组数。

每组数据第一行为一个整数N,表示订单数目。

接下来N行,每行三个整数x, y, w,表示一个订单。(x, y)表示建造出的金字塔的顶点,w表示收益。

输出

对于每组数据输出一行"Case #X: Y",X表示数据编号(从1开始),Y表示最大利润,四舍五入到小数点后两位。

数据范围

1 ≤ T ≤ 20

0 ≤ w ≤ 107

小数据

1 ≤ N ≤ 20

0 ≤ x, y ≤ 20

大数据

1 ≤ N ≤ 1000

0 ≤ x, y ≤ 1000


样例输入
3
2
2 2 3
6 2 5
3
1 1 1
2 2 3
3 3 5
3
1 1 1
2 2 3
3 3 6
样例输出
Case #1: 1.00
Case #2: 0.00
Case #3: 1.00

解答:

         另类的背包问题。把金字塔看成物品;每个金字塔有左端点和右端点,把背包中的金字塔的最右端点看成背包当前重量。
         把金字塔物品按照左端点的坐标排序。动态规划,状态 d【i】【j】(n>=i>=1),代表使用前i个金字塔,并且所用金字塔的最右端点为j时的最大收益。注意除了d【i】【0】,其他都初始化为负无穷大(表示不存在这种方案,至于为什么设定为无穷小,是为了下面进行更新计算时方便)。
        递推方程: d【i】【j】  =    case1  j >t【i】.r :  max{ d【i-1】【j】, d【i-1】【j】 & t【i】},      
                                                         case2  j=t【i】.r :     max{d【i-1】【k】 & t【i】,0<=k<=j }
 
                             d【0】【j】 = -1e30,  d【i】【0】 = 0 
                            & 代表加入新的金字塔 。
        实现:
        依次对每个金字塔t【i】,更新所有d 【x】
        更新过程中 
             case 1:  x>=t[i].r, 则 已用的金字塔们中存在一个金字塔<x0, x>且x0<=t[i].l, x>=t[i].r. 所有更新d[x] = d[x] +t[i].w
             case 2:  x<t[i].r && x>=t[i].l :  则更新d[t[i].r] = max{d[t[i].r], d[x]+t[i].w- cost(t[i].r-t[i].l) + cost(x-t[i].l) }
             case 3:  x<t[i].l: 则更新 d[t[i].r] = max{d[t[i].r], d[x]+t[i].w- cost(t[i].r-t[i].l) }
            更新完所有d[x],再更新一下d[t[i].r]为只有金字塔t[i]时的情形: d[t[i].r] = max{ d[t[i].r], t[i].w-cost(t[i].r-t[i].l) } 。(或者设定d【0】=0,则当x=0时,会更新到这一步:d[t[i].r] = max(d[t[i].r], d[0] + t[i].w -... } )

#include <iostream>
#include <algorithm>
#include <string.h>
#include <stdio.h>
using namespace std;
#define long long int LL
struct tt{
    int l, r, w;
    friend bool operator<(const tt&a, const tt&b){ return a.l<=b.l; } //@error: not operator (), but <
}ts[1000];

double d[3001];
#define MINN (-0xffffff) //@opt: or (-1e30)
double cost(int x){
    return x*x/4.0;//@error: not /4, which return int not double
}
int main(){
    int N;
    cin>>N;
    for(int ii=1; ii<=N; ii++){
        int n; cin>>n;
        int m = 0;
        for(int i = 0; i<n; i++){
            int x, y, w; cin>>x>>y>>w;
            ts[i] = (tt){x-y+1000, x+y+1000, w};
            m = max(m, x+y+1000);
        }
        sort(ts, ts+n);
        for(int i = 0; i<=m; i++) d[i] = MINN; d[0] = 0;
        for(int i = 0; i<n; i++){
            for(int j = m; j>=0; j--){
                if(d[j]==MINN) continue; //@opt: can be removed
                if(j>ts[i].r){
                    d[j] = max(d[j], d[j] + ts[i].w);
                }
                else{
                    int l = ts[i].l, r = ts[i].r;
                    if(l <= j)
                         d[r] = max(d[r], d[j]+ts[i].w-cost(r-l)+cost(j-l));
                    else d[r] = max(d[r], d[j]+ts[i].w-cost(r-l));
                }
            }
        }
        double mm = 0;
        for(int i = 0; i<=m; i++) mm = max(mm, d[i]);
        printf("Case #%d: %.2f\n", ii, mm);//@opt: %.2lf
    }
    return 0;
}





题目3 : 质数相关

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

两个数a和 b (a<b)被称为质数相关,是指a × p = b,这里p是一个质数。一个集合S被称为质数相关,是指S中存在两个质数相关的数,否则称S为质数无关。如{2, 8, 17}质数无关,但{2, 8, 16}, {3, 6}质数相关。现在给定一个集合S,问S的所有质数无关子集中,最大的子集的大小。

输入

第一行为一个数T,为数据组数。之后每组数据包含两行。

第一行为N,为集合S的大小。第二行为N个整数,表示集合内的数。

输出

对于每组数据输出一行,形如"Case #X: Y"。X为数据编号,从1开始,Y为最大的子集的大小。

数据范围

1 ≤ T ≤ 20

集合S内的数两两不同且范围在1到500000之间。

小数据

1 ≤ N ≤ 15

大数据

1 ≤ N ≤ 1000

样例输入
3
5
2 4 8 16 32
5
2 3 4 6 9
3
1 2 3
样例输出
Case #1: 3
Case #2: 3
Case #3: 2


解答:

这是一个二分图。通过最大流找最小点集覆盖,从而间接找到最大独立点集。

错误思路一:

建图:最小的数作为节点放在左边。 从小到大开始:凡是与左边任意数互斥的数都放到右边,反之放到左边。所有互斥关系都需要连边。

证明:假设左边有数x,y ,右边有数xp,yq (p,q是质数),显然x、y同在左边,不会互斥。如果xp和yq互斥,则能够推出x和y互斥,显然这是不成立的(如果xp和yq互斥,则xpk = yq, p\q\k是质数,则 case1: x = sq, 则y=spk,这与pqk为质数矛盾; case 2: p=sq,则p=q,则xk = y,这与xy不互斥矛盾; case 3: k=sq,同case2;)。所以右边任意节点之间也不会互斥。所以这是二分图。

接下来,考虑从左边和右边取出多个数,保证取的数的节点之间没有边。要找到最多的数。

正确思路二:

凡是质数因子个数为奇数的放在左边,偶数的放在右边。如 6=2*3,质因子个数为2,放在右边。

证明: 质因子个数同为奇数或同为偶数的两个数不可能互斥,因为互斥的两个数质因子个数相差1。

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
struct bian{
	int next,point,f;
}b[1100000];
int t,f[1100],n,prime[510000],w[510000],lim=500000,p[1100],len,dst[1100],x[1100],totpoint,pd[1100],inf=1100;
int check(int k1,int k2){     //k1 k2是否互斥
	if (k1>k2) swap(k1,k2);
	if (k2%k1==0&&w[k2]==w[k1]+1) return 1; else return 0;
}
void ade(int k1,int k2,int k3) //添加边 k1->k2
{
    len++; b[len].point=k2; b[len].next=p[k1]; b[len].f=k3; p[k1]=len;
}
void add(int k1,int k2,int k3)
{
    ade(k1,k2,k3); ade(k2,k1,0);
}
bool bfs() // 通过最大流找最小覆盖(dinic:bfs,change)
{
    int head=1,now=0,i,j;
    memset(dst,0xff,sizeof dst);
    memset(pd,0x00,sizeof pd);
    x[1]=0; pd[0]=1; dst[0]=0;
    while (head>now)
    {
        now++; i=p[x[now]];
        while (i!=-1)
        {
            j=b[i].point;
            if ((b[i].f)&&(!pd[j]))
            {
                pd[j]=1; dst[j]=dst[x[now]]+1;
                if (j==totpoint)
                {
                    return 1;
                }
                head++; x[head]=j;
            }
            i=b[i].next;
        }
    }
    return pd[totpoint];
}
int change(int k1,int k2)// (dinic:bfs,change)
{
    if ((k1==totpoint)||(k2==0)) return k2;
    int num=0,k,i,j;
    i=p[k1];
    while (i!=-1)
    {
        j=b[i].point;
        if ((b[i].f)&&(dst[k1]+1==dst[j]))
        {
            k=change(j,min(k2,b[i].f));
            k2=k2-k; num+=k;
            b[i].f=b[i].f-k; b[i^1].f+=k;
            if (k2==0)
            {
                break;
            }
        }
        i=b[i].next;
    }
    if (!num)
    {
        dst[k1]=-1;
    }
    return num;
}
int dinic(){
    int now=0; while (bfs()) now+=change(0,inf); return now;
}
int solve(){
	scanf("%d",&n); len=-1;
	memset(p,0xff,sizeof p);
	for (int i=1;i<=n;i++) scanf("%d",&f[i]);
	int ans=0; totpoint=n+1;
	for (int i=1;i<=n;i++) 
                if (w[f[i]]%2==0) add(0,i,1); //左节点:S->i
                else add(i,n+1,1);            //右节点:i->T
	for (int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)
			if (w[f[i]]%2==0&&w[f[j]]%2&&check(f[i],f[j])) add(i,j,1);// i=>j
	return n-dinic(); //最大独立点集 = n - 最小覆盖点集
}
void make(){
	w[1]=0; int len=0;  // w[i]记录i的质数因子个数
	for (int i=2;i<=lim;i++){
		if (w[i]==0){
			prime[++len]=i; w[i]=1;//i是素数,质因子个数为1
		}
		for (int j=1;j<=len&&i*prime[j]<=lim;j++)
		
			if (i%prime[j]==0){
				w[i*prime[j]]=w[i]+1; break;
			} 
                        else w[i*prime[j]]=w[i]+1;
	}
}
int main(){
	scanf("%d",&t); make();
	for (int i=1;i<=t;i++){
		printf("Case #%d: %d\n",i,solve());
	}
	return 0;
}




初赛第二场

题目1 : 扑克牌

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

一副不含王的扑克牌由52张牌组成,由红桃、黑桃、梅花、方块4组牌组成,每组13张不同的面值。现在给定52张牌中的若干张,请计算将它们排成一列,相邻的牌面值不同的方案数。

牌的表示方法为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,表示一张牌。每组数据中的扑克牌各不相同。

输出

对于每组数据输出一行,形如"Case #X: Y"。X为数据组数,从1开始。Y为可能的方案数,由于答案可能很大,请输出模264之后的值。

数据范围

1 ≤ T ≤ 20000

小数据

1 ≤ N ≤ 5

大数据

1 ≤ N ≤ 52


样例输入
5
1 TC
2 TC TS
5 2C AD AC JC JH
4 AC KC QC JC
6 AC AD AS JC JD KD
样例输出
Case #1: 1
Case #2: 0
Case #3: 48
Case #4: 24
Case #5: 120

解答:



题目2 : 攻城略地

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

A、B两国间发生战争了,B国要在最短时间内对A国发动攻击。已知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间有一条道路。

输出

对于每组数据输出一行,格式为"Case #X: Y"。X表示数据编号(从1开始),Y为答案。

数据范围

1 ≤ T ≤ 30

k ≤ m

0 ≤ w ≤ 108

小数据

1 ≤ n ≤ 1000

0 ≤ m ≤ 5000

大数据

1 ≤ n ≤ 106

0 ≤ m ≤ 106


样例输入
2
4 4 2
6 5 3 4
1 2
1 3
2 3
2 4
4 4 4
6 5 3 4
1 2
1 3
2 3
2 4
样例输出
Case #1: 7
Case #2: 18


解答

一道水题,贪心法可解。问题是当时没想通一个关键点:
 给定一棵包含n个节点树,任选其中m个节点(m<=n),一定能够找到一种切割方法,把原树切割成m个连通分支,且这m个点分别分布在不同分支上。
所以很简单了: 
原图是s个连通分支,初始代价为每个分支的最小点权值之和。
现在对该图删边,等价于进行切割。
题目要求切割k次,由于原图的s个连通分支里一共有 j 条冗余边,可以进行删除并且不产生新连通分支。删完之后,原图变成一棵森林。
那么在删除剩余k-j条边的过程中,将会产生k-j个分支。
我们现在从这s个连通分支中  找到 除开各分支最小点之后的剩余n-s个点,取它们中权值最小的k-j个点,并把他们和同分支最小权值点彼此分割到不同分支上。(如果k<=j,那么不产生新分支)
ans=E(min(Vi | i 属于Bj )| 1<=j<=s) + E(min(Vi| i)

#include <iostream>
using namespace std;
#include <algorithm>
typedef long long int ll;
#include <queue>
#include <string.h>
#define MAXN 1000001
vector<int>  es[MAXN];
int vis[MAXN];  // branch number of vertex
int vs[MAXN];   // price of vertex 
int vv[MAXN];   // min vertex of branch
int ov[MAXN];   // price of non-min vertex
void bfs(int u, int t){
    queue<int> q;
    vis[u] = t, q.push(u);
    while(!q.empty()){
        int u = q.front(); q.pop();
        for(int i = 0; i<es[u].size(); i++){
            if(vis[es[u][i]]<0){
                int s = es[u][i];
                vis[s] = t, q.push(s);
            }
        }
    }
}
int main(){
    int N; scanf("%d", &N); //@error: cin will cause time limit exceed, we need to use scanf
    for(int ii=1; ii<=N; ii++){
        int n, m, k; scanf("%d %d %d", &n, &m, &k);
        for(int i = 0; i<n; i++){
            es[i].clear();
            scanf("%d", &vs[i]);
        }
        for(int i = 0; i<m; i++){
            int a, b; scanf("%d %d", &a, &b); a--, b--;
            es[a].push_back(b), es[b].push_back(a);
        }
        
        memset(vis, -1, sizeof(int)*n);
        int s = 0; // number of branches
        for(int i = 0; i<n; i++){
            if(vis[i]<0){
                bfs(i, s++);
            }
        }
        
        memset(vv, -1, sizeof(int)*s); // min vertex of a branch
        for(int i = 0 ; i<n; i++){
            int j = vis[i];
            if(vv[j]<0) vv[j] = i;
            else if(vs[vv[j]]>vs[i]) vv[j] = i;
        }
        
        ll op = 0;   //@error: int op will cause overflow!!!( 10^8 + ... > int)
        int idx = 0;
        for(int i = 0 ; i<n; i++){
            if(vv[vis[i]] != i) ov[idx++] = vs[i];
            else op += vs[i];
        }
        
        int bn = min(k - (m-(n-s)), n-s); //bn is count of new branches. @error: k may be bigger than m, in which case bn = n-s
        printf("Case #%d: ", ii);
        if(bn>0){
            sort(ov, ov+idx);
            for(int i = 0; i<bn; i++){
                op += ov[i];
            }
        }
        printf("%ld\n", op); 
    }
    return 0;
}





题目3 : 八卦的小冰

时间限制: 2000ms
单点时限: 1000ms
内存限制: 256MB

描述

小冰是个八卦的人,最近她对一个社交网站很感兴趣。

由于小冰是个机器人,所以当然可以很快地弄清楚这个社交网站中用户的信息啦。

她发现这个社交网站中有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”:询问八卦度

输出

对于每组数据首先输出一行"Case #X:",X为测试数据编号。

接下来对于每一个询问,输出一行包含询问的八卦度。

数据范围

1 ≤ T ≤ 20

1 ≤ x, y ≤ N

0 ≤ z ≤ 100000

小数据

1 ≤ N, M ≤ 100

1 ≤ Q ≤ 1000

大数据

1 ≤ N, M, Q ≤ 100000

样例输入
1
3 2 8
0 1 0
1 2 1
1 3 1
3
1 1
1 2
3
2 2 3 2
3
1 2
3
样例输出
Case #1:
1
2
2
3

解答:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值