1. 程式人生 > >【程式設計之美】初賽2015 待填坑

【程式設計之美】初賽2015 待填坑

初賽第一場

題目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

解答: