2017百度之星資格賽總結
第一題沒有什麼說的,題目我都沒看懂,並沒有理解他的包圍,賽後大佬的程式碼我也看了,嗯,沒看懂。。。
第二題是資料水了,正解為全域性最小割,我並沒這樣做出來,我只是單純的用並查集刪了點(可用單鏈卡掉),竟然過了。。(據說資料水到不用並查集也可以過)
度度熊的王國戰略
Accepts: 674 Submissions: 6056 Time Limit: 40000/20000 MS (Java/Others) Memory Limit: 32768/132768 K (Java/Others) Problem Description度度熊國王率領著喵哈哈族的勇士,準備進攻嘩啦啦族。
嘩啦啦族是一個強悍的民族,裡面有充滿智慧的謀士,擁有無窮力量的戰士。
所以這一場戰爭,將會十分艱難。
為了更好的進攻嘩啦啦族,度度熊決定首先應該從內部瓦解嘩啦啦族。
第一步就是應該使得嘩啦啦族內部不能同心齊力,需要內部有間隙。
嘩啦啦族一共有n個將領,他們一共有m個強關係,摧毀每一個強關係都需要一定的代價。
現在度度熊命令你需要摧毀一些強關係,使得內部的將領,不能通過這些強關係,連成一個完整的連通塊,以保證戰爭的順利進行。
請問最少應該付出多少的代價。
Input本題包含若干組測試資料。
第一行兩個整數n,m,表示有n個將領,m個關係。
接下來m行,每行三個整數u,v,w。表示u將領和v將領之間存在一個強關係,摧毀這個強關係需要代價w
資料範圍:
2<=n<=3000
1<=m<=100000
1<=u,v<=n
1<=w<=1000
Output對於每組測試資料,輸出最小需要的代價。
Sample Input2 1 1 2 1 3 3 1 2 5 1 2 4 2 3 3Sample Output
1 3
程式碼:
#include <cstdio> #include <cstring> #include <iostream> #define inf 0x3f3f3f3f using namespace std; long long num[100010]; int n,m; int pre[3001]; void init(){ for(int i=1;i<3001;i++) pre[i]=i; } int Find(int x){ if(x!=pre[x]) return pre[x]=Find(pre[x]); return x; } void mix(int x,int y){ int xx=Find(x),yy=Find(y); if(xx!=yy){ pre[xx]=yy; } } int main(){ int a,b,c; while(~scanf("%d %d",&n,&m)){ bool flag=false; memset(num,0,sizeof(num)); init(); for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); if(a!=b){ num[a]+=c; num[b]+=c; mix(a,b); } } int cnt=0; for(int i=1;i<=n;i++){ if (pre[i]==i) cnt++; } if(cnt==1) flag=true; if(flag){ long long ans=inf; for(int i=1;i<=n;i++){ if(ans>num[i]) ans=num[i]; } printf("%I64d\n",ans); } else { printf("0\n"); } } return 0; }
正解:
#include<bits/stdc++.h>
#define N 3005
#define mk make_pair
using namespace std;
int a[N][N],T[N][N],F[N],vis[N];
int dead[N],pos[N],u,v,w,i,j,n,m,ans;
struct Heap{
int id[N],len;
void up(int x){
for (;x>1;x>>=1)
if (F[id[x]]>F[id[x>>1]])
swap(pos[id[x]],pos[id[x>>1]]),swap(id[x],id[x>>1]);
else return;
}
void pop(){
pos[id[len]]=0;if ((--len)==0) return;
id[1]=id[len+1];pos[id[1]]=1;
for (int x=1;(x<<1)<=len;){
int y=((x<<1)==n||F[id[x<<1]]>F[id[x<<1|1]])?(x<<1):(x<<1|1);
if (F[id[x]]>=F[id[y]]) return;
swap(pos[id[x]],pos[id[y]]);swap(id[x],id[y]);x=y;
}
}
void add(int x){
if (!pos[x]) id[pos[x]=++len]=x,up(len);
else up(pos[x]);
}
void clear(){len=0;}
}G;
void work(){
for (int i=1;i<=n;i++)
F[i]=-1e9,vis[i]=pos[i]=0;
int S,p,q;
for (int i=1;i<=n;i++)
if (!dead[i]) {S=i;break;}
G.clear();F[S]=0;
G.len=1;pos[G.id[1]=S]=G.id[1];
while (G.len){
int x=G.id[1];vis[x]=1;G.pop();
for (i=1;i<=*a[x];i++){
int y=a[x][i];
if (vis[y]) continue;
F[y]+=T[x][y];G.add(y);
}
p=q;q=x;
}int sum=0;
for (int i=1;i<=*a[q];i++)
sum+=T[q][a[q][i]];
//printf("%d\n",sum);
ans=min(ans,sum);
for (int i=1;i<=n;i++)
if (!dead[i]&&i!=q&&i!=p)
if (T[q][i]&&!T[p][i]) a[p][++*a[p]]=i;
for (int i=1;i<=n;i++)
if (!dead[i]&&i!=p&&i!=q)
T[i][p]+=T[i][q],T[p][i]+=T[q][i];
dead[q]=1;
//printf("%d %d\n",p,q);
int debug=(p==4&&q==3);
for (int i=1;i<=n;i++)
if (!dead[i]&&i!=q){
int j=1;
for (;j<=*a[i];j++)
if (a[i][j]==q) break;
if (j<=*a[i]){
for (;j<*a[i];j++)
a[i][j]=a[i][j+1];
--(*a[i]);
}
if (!T[i][p]) continue;
for (j=1;j<=*a[i];j++)
if (a[i][j]==p) break;
if (j>(*a[i])) a[i][++*a[i]]=p;
}
//printf("Delete:%d Sum:%d\n",q,sum);
//for (int i=1;i<=n;i++)
//if (!dead[i]) for (int j=1;j<=*a[i];j++)
//printf("%d->%d %d\n",i,a[i][j],T[i][a[i][j]]);
}
int main(){
while (scanf("%d%d",&n,&m)!=EOF){
if (!n) return 0;
memset(T,0,sizeof(T));
for (i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&w);
if (u==v) continue;
T[u][v]+=w;T[v][u]+=w;
}
//printf("%d\n",T[1][1]);
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (T[i][j]) a[i][++*a[i]]=j;
/*for (int i=1;i<=n;i++)
for (int j=1;j<=*a[i];j++)
printf("%d->%d %d\n",i,a[i][j],T[i][a[i][j]]);*/
ans=2e9;
for (int tmp=min(n-1,1000);tmp;tmp--)
work();
printf("%d\n",ans);
for (i=1;i<=n;i++) *a[i]=dead[i]=0;
}
}
第三個題是簽到題,我們先判斷是否能夠打敗所有怪獸,然後就是一個裸揹包。
度度熊與邪惡大魔王
Accepts: 3666 Submissions: 22474 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description度度熊為了拯救可愛的公主,於是與邪惡大魔王戰鬥起來。
邪惡大魔王的麾下有n個怪獸,每個怪獸有a[i]的生命值,以及b[i]的防禦力。
度度熊一共擁有m種攻擊方式,第i種攻擊方式,需要消耗k[i]的晶石,造成p[i]點傷害。
當然,如果度度熊使用第i個技能打在第j個怪獸上面的話,會使得第j個怪獸的生命值減少p[i]-b[j],當然如果傷害小於防禦,那麼攻擊就不會奏效。
如果怪獸的生命值降為0或以下,那麼怪獸就會被消滅。
當然每個技能都可以使用無限次。
請問度度熊最少攜帶多少晶石,就可以消滅所有的怪獸。
Input本題包含若干組測試資料。
第一行兩個整數n,m,表示有n個怪獸,m種技能。
接下來n行,每行兩個整數,a[i],b[i],分別表示怪獸的生命值和防禦力。
再接下來m行,每行兩個整數k[i]和p[i],分別表示技能的消耗晶石數目和技能的傷害值。
資料範圍:
1<=n<=100000
1<=m<=1000
1<=a[i]<=1000
0<=b[i]<=10
0<=k[i]<=100000
0<=p[i]<=1000
Output對於每組測試資料,輸出最小的晶石消耗數量,如果不能擊敗所有的怪獸,輸出-1
Sample Input1 2 3 5 7 10 6 8 1 2 3 5 10 7 8 6Sample Output
6 18程式碼:
#include <cstdio>
#include <cstring>
#include <iostream>
#define inf 0x3f3f3f3f
#define LL long long
using namespace std;
int n,m,a[100010],b[100010];
int k[10010],p[10010];
long long dp[12][1005<<1];
int main(){
while(~scanf("%d%d",&n,&m)){
int mx0=0,mx1=0;
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
mx0=max(mx0,b[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&k[i],&p[i]);
mx1=max(mx1,p[i]);
}
if(mx0>=mx1){
printf("-1\n");
continue;
}
memset(dp,inf,sizeof(dp));
for(int x=0;x<=10;x++){
dp[x][0]=0;
for(int i=1;i<=m;i++){
if(p[i]<=x)
continue;
for(int j=p[i]-x;j<=2000;j++){
dp[x][j]=min(dp[x][j],dp[x][j-(p[i]-x)]+k[i]);
}
}
for(int i=2000;i>0;i--){
dp[x][i]=min(dp[x][i],dp[x][i+1]);
}
}
LL ans=0;
for(int i=1;i<=n;i++){
ans+=dp[b[i]][a[i]];
}
printf("%I64d\n",ans);
}
return 0;
}
第四個題是一個比較有難度的dp,這個題多虧了qls和csy在ACfun裡的提示,題意沒有很清楚,這其實就是一個01揹包,但是要求優先最大化總得分,然後最小化序列之和,最後最大化花費,要求輸出字典序最小的解,在不打飯的情況下不輸出空行(我PE了4發...)。
度度熊的午飯時光
Accepts: 823 Submissions: 9241 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Problem Description度度熊最期待每天的午飯時光,因為早飯菜品清淡,晚飯減肥不敢吃太多(胖紙的憂傷T.T)。
百度食堂的午餐超級豐富,祖國各大菜系應有盡有,度度熊在每個視窗都有愛吃的菜品,而且他還為喜愛的菜品打了分,吃貨的情懷呀(>.<)。
但是,好吃的飯菜總是很貴,每天的午飯預算有限,請幫度度熊算一算,怎樣打飯才能買到的最好吃的飯菜?(不超過預算、不重樣、午餐等分最高的情況下,選擇菜品序號加和最小,加和相等時字典序最小的組合)
Input第一行一個整數T,表示T組資料。 每組測試資料將以如下格式從標準輸入讀入:
B
N
score_1 cost_1
score_2 cost_2
:
score_N cost_N
第一行,正整數B(0 <= B <= 1000),代表午餐的預算。
第二行,正整數N (0 <= N <= 100),代表午餐可選的菜品數量
從第三行到第 (N + 2) 行,每行兩個正整數,以空格分隔,score_i表示菜品的得分,cost_i表示菜品的價格(0 <= score_i, cost_i <= 100)。
Output對於每組資料,輸出兩行: 第一行輸出:"Case #i:"。i代表第i組測試資料。 第二行輸出菜品的總得分和總花費,以空格分隔。 第三行輸出所選菜品的序號,菜品序號從1開始,以空格分隔。
Sample Input2 29 6 9 10 3 4 6 5 7 20 10 9 15 11 0 2 2 23 10 12Sample Output
Case #1: 34 29 2 3 5 6 Case #2: 0 0
這個題開始用揹包的思路去想的
pre[i][j].first代表的是你取的最後一個物品編號,因為dp就是物品編號小到大的順序dp的,所以這個一定是當前最小
dp[i][j].second代表的是當前序列之和,如果最大值相同,取序號和最小
dp[i][j].first代表的是遍歷到第i個物品總費用為j的情況下的最大價值
然後看程式碼應該就懂了
程式碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
struct node{
int score,cost;
}xin[10001];
pair<int,int>dp[102][1001],pre[102][1001];
int p[10000];
int main(){
int Case=0,t;
int b,n;
scanf("%d",&t);
while(t--){
Case++;
scanf("%d%d",&b,&n);
for(int i=1;i<=n;i++){
scanf("%d%d",&xin[i].score,&xin[i].cost);
}
for(int i=0;i<=n;i++){
for(int j=0;j<=b;j++){
dp[i][j].first=dp[i][j].second=-1;
pre[i][j].first=pre[i][j].second=-1;
}
}
dp[0][0].first=dp[0][0].second=0;
int ans1=0,ans2=0,x,y;
for(int i=1;i<=n;i++){
for(int j=xin[i].cost;j<=b;j++){
for(int k=0;k<i;k++){
if(dp[k][j-xin[i].cost].first==-1)
continue;
if(dp[i][j].first<dp[k][j-xin[i].cost].first+xin[i].score){
dp[i][j].first=dp[k][j-xin[i].cost].first+xin[i].score;
dp[i][j].second=dp[k][j-xin[i].cost].second+i;
pre[i][j].first=k;
pre[i][j].second=j-xin[i].cost;
}
else if(dp[i][j].first==dp[k][j-xin[i].cost].first+xin[i].score){
if(dp[i][j].second>dp[k][j-xin[i].cost].second+i){
dp[i][j].second=dp[k][j-xin[i].cost].second+i;
pre[i][j].first=k;
pre[i][j].second=j-xin[i].cost;
}
}
}
if(dp[i][j].first>ans1){
ans1=dp[i][j].first;
ans2=dp[i][j].second;
x=i,y=j;
}
else if(dp[i][j].first==ans1){
if(ans2>dp[i][j].second){
ans2=dp[i][j].second;
x=i,y=j;
}
}
}
}
printf("Case #%d:\n",Case);
printf("%d %d\n",ans1,y);
int cnt=0;
while(x!=0&&x!=-1){
p[cnt++]=x;
int k1=x,k2=y;
x=pre[k1][k2].first;
y=pre[k1][k2].second;
}
for(int i=cnt-1;i>=0;i--){
printf(i!=0?"%d ":"%d\n",p[i]);
}
}
return 0;
}
對於第五題來說,是一個卡特蘭數,金桔告訴我的,我並沒有看出來,卡特蘭數我練的也不熟,想了一個多小時沒有思路就放棄了,賽後看了大牛們的程式碼,卡特蘭數+擴充套件歐幾里得,我還是慢慢研究這個題吧。