2017-2018 ACM-ICPC German Collegiate Programming Contest (GCPC 2017)
题号 | A | B | C | D | E | F | G | H | I | J | K | L |
状态 | . | . | Ο | Ο | . | Ø | Ο | . | Ο | . | Ο | . |
Ο:当场
Ø:已补
. : 待补
A Drawing Borders
待补。
B Buildings
待补。
C Joyride
Code:kk
Thinking:kk
题意游乐场有n个设施,有m条人行道,游乐设施会花费ti的时间和pi的钱,人行道需要花费t的时间,你需要用最少的钱恰好游玩x的时间,起点是1,终点是1,求最少的钱是多少4,并且1必须经过两次。
这道题做的我心情复杂。。
先说解法吧,其实很简单,$dp[i][t]$表示到第i个节点,已经使用了t的时间的最小花费,那么状态转移方程就是$dp[v][t]=min(do[u][t-tv]$,然后用队列存放状态,跑一个类似dijkstra堆优化的东西,只不过不用优先队列,特别注意1必须经过两次,所以一开始要判断一下一号节点的时间。
是不是很简单呢。。然而为了写的方便,我把每个节点自己和自己都连了一个环,然后这样的边数就变成了3*n,而我链式前向星的结构体只开了两倍,显示wa9,连带着队友一起自闭了,其他好多题可以做的啊啊啊啊啊
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=2010; const ll inf=0x3f3f3f3f3f3f3f3f; ll dp[maxn][maxn],p[maxn],maxx; bool vis[maxn][maxn]; int n,m,x,t,head[maxn],tot; int tim[maxn]; struct node{ int u,t; }st; struct edge{ int to,Next; ll w; }a[maxn<<1]; inline void addv(int u,int v,int w){ a[++tot].to=v; a[tot].w=w; a[tot].Next=head[u]; head[u]=tot; } void bfs(){ clr(dp,inf); if(tim[1]>=x)return; dp[1][tim[1]]=p[1]; queue<node>q; q.push({1,tim[1]}); // vis[1][tim[1]]=1; while(!q.empty()) { st=q.front(); q.pop(); int u=st.u,ti=st.t; // vis[u][ti]=0; if(st.t>x)continue; for(int i=head[u];i!=-1;i=a[i].Next) { int v=a[i].to; int cotime=a[i].w; if(cotime+ti+tim[v]>x)continue; // printf("dbbug\n"); if(dp[v][cotime+ti+tim[v]]>dp[u][ti]+p[v]){ dp[v][cotime+ti+tim[v]]=dp[u][ti]+p[v]; // printf("%d %d %d\n",u,v,dp[v][cotime+ti+tim[v]]); // if(!vis[v][cotime+ti+tim[v]]){ q.push({v,cotime+ti+tim[v]}); // vis[v][cotime+ti+tim[v]] = 1; // } } } } maxx=min(maxx,dp[1][x]); } void pop() { for(int i=1;i<=101001;i++) for(int j=1;j<=101010;j++) ; } int main(){ while(cin>>x) { clr(head,-1),tot=0; scanf("%d%d%d",&n,&m,&t); for(int i=1;i<=m;i++) { int u,v,w; scanf("%d%d",&u,&v); if(u==v) pop(); addv(u,v,t); addv(v,u,t); } for(int i=1;i<=n;i++) { scanf("%d%lld",&tim[i],&p[i]); addv(i,i,0); } maxx=inf; bfs(); if(maxx==inf)puts("It is a trap."); else printf("%lld\n",maxx); } }
D.Pants On Fire
Thinking:zz
Code:zz
题意:先给n个谁比谁菜的关系,然后给m个谁比谁菜的关系,判断这m个关系是正确的、错误的还是不明确的。
题解:按n个关系建图,然后直接bfs暴力判断这m个关系是不是对的。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=110010; char c[1010],d[1010],tmp[1010]; unordered_map<string,int>mp; vector<int>ve[1010]; int v[550]; int main() { int n,m,i,j,k,cnt,xx,yy,pos,si,qq,ans; while(~scanf("%d %d",&n,&m)) { for(i = 0;i <= 1000;i++) { ve[i].clear(); } cnt = 1; for(i = 0;i < n;i++) { scanf("%s",c); scanf("%s",tmp); scanf("%s",tmp); scanf("%s",tmp); scanf("%s",d); if(!mp[c]) { mp[c] = cnt++; } if(!mp[d]) { mp[d] = cnt++; } xx = mp[c]; yy = mp[d]; ve[yy].push_back(xx); } for(i = 0;i < m;i++) { scanf("%s",c); scanf("%s",tmp); scanf("%s",tmp); scanf("%s",tmp); scanf("%s",d); xx = mp[c]; yy = mp[d]; if(xx == 0 || yy == 0) { printf("Pants on Fire\n"); continue; } else { queue<int>q; q.push(yy); memset(v,0,sizeof(v)); v[yy] = 1; qq = 0; //printf("111\n"); while(!q.empty()) { //printf("222\n"); pos = q.front(); q.pop(); //printf("2222 %d\n",pos); si = ve[pos].size(); //printf("22222\n"); for(j = 0;j < si;j++) { //printf("333\n"); if(!v[ve[pos][j]]) { v[ve[pos][j]] = 1; if(xx == ve[pos][j]) { qq = 1; break; } //printf(" %d\n",ve[pos][j]); q.push(ve[pos][j]); } } if(qq) { break; } } //printf("999\n"); if(qq) { ans = 1; } else { //printf("111\n"); queue<int>qqq; qqq.push(xx); memset(v,0,sizeof(v)); v[xx] = 1; qq = 0; while(!qqq.empty()) { pos = qqq.front(); qqq.pop(); si = ve[pos].size(); for(j = 0;j < si;j++) { if(!v[ve[pos][j]]) { v[ve[pos][j]] = 1; if(yy == ve[pos][j]) { //printf("5555\n"); qq = 1; break; } qqq.push(ve[pos][j]); } } if(qq) { break; } } if(qq) { ans = 2; } else { ans = 3; } } } if(ans == 1) { printf("Fact\n"); } else if(ans == 2) { printf("Alternative Fact\n"); } else { printf("Pants on Fire\n"); } } } }
E Perpetuum Mobile
待补
F Plug It In!
补题:kk
这题看上去就很模板的样子。。比赛的时候没做真是太蠢了。。
暴力枚举每个点,然后每次都重新跑二分图显然是会超时的,但是我们发现,如果新增加两个节点,就可以直接在原图的增广路径上来跑二分图,这样只需要对新增加的两个点跑就可以了,所以就把原图复制一下,(只需要复制girl数组就好)然后暴力枚举每个点。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1510; vector<int >ve[maxn]; int girl[maxn],vis[maxn],match[maxn]; int n,m,k; bool dfs(int x) { for(auto v:ve[x]) { if(vis[v])continue; vis[v]=1; if(girl[v]==0||dfs(girl[v])){ girl[v]=x; return true; } } return false; } bool dfs_2(int x) { for(auto v:ve[x]) { if(vis[v])continue; vis[v]=1; if(match[v]==0||dfs_2(match[v])){ match[v]=x; return true; } } return false; } int main(){ while(cin>>m>>n>>k) { for(int i=1;i<=k;i++) { int u,v; scanf("%d%d",&u,&v); ve[u].push_back(v); } int ans=0; for(int i=1;i<=m;i++) { clr(vis,0); if(dfs(i))ans++; } int maxx=ans; for(int i=1;i<=m;i++) { for(int j=1;j<=n;j++) { match[j]=girl[j]; } ve[1+m].clear(),ve[2+m].clear(); for(int j=0;j<ve[i].size();j++) { ve[1+m].push_back(ve[i][j]); ve[2+m].push_back(ve[i][j]); } int tep=ans; for(int j=m+1;j<=m+2;j++) { clr(vis,0); if(dfs_2(j))tep++; } maxx=max(maxx,tep); } cout<<maxx<<endl; } }
G Water Testing
皮克定理
皮克定理:指一个计算点阵中顶点在格点上的多边形面积公式,该公式可以表示为$2S=2a+b-2$,其中a表示多边形内部的点数,b表示多边形边界上的点数,S表示多边形的面积。
Code:zz
Thinking:zz
题意:按顺序给定一个凸包上的所有点,判断凸包内有多少个整数点(凸包上的点不算)。
题解:设凸包上的点数为a,凸包内的点数为b,凸包的面积为s,则s=b+a/2-1(皮克定理),这样一来算出s(用叉乘算)和a(用gcd算)就行了。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=1010; const ll inf=0x3f3f3f3f3f3f3f3f; struct ss { long long a,b; }z[100010],zz; inline long long Gcd(long long n,long long m) { long long r; while(n % m != 0) { r = n % m; n = m; m = r; } return m; } int main(){ int n,i,j; long long s,a,xx,yy,tmp,ans; while(~scanf("%d",&n)) { for(i = 1;i <= n;i++) { scanf("%lld %lld",&z[i].a,&z[i].b); } z[n + 1] = z[1]; s = 0; for(i = 1;i <= n;i++) { s += z[i].a * z[i + 1].b - z[i].b * z[i + 1].a; } //printf(" s = %lld\n",s); if(s <= 0) { for(i = 1;i <= n / 2;i++) { zz = z[i]; z[i] = z[n - i + 1]; z[n - i + 1] = zz; } z[n + 1] = z[1]; s = 0; for(i = 1;i <= n;i++) { s += z[i].a * z[i + 1].b - z[i].b * z[i + 1].a; } } a = 0; for(i = 1;i <= n;i++) { xx = z[i].a - z[i + 1].a; yy = z[i].b - z[i + 1].b; if(xx < 0) { xx = -xx; } if(yy < 0) { yy = -yy; } if(xx == 0 || yy == 0) { xx = max(xx,yy); a += xx; } else { tmp = Gcd(xx,yy); a += tmp; } } ans = s + 2 - a; printf("%lld\n",ans / 2); } }
H Ratatöskr
待补
I Überwatch
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=110010; int n,m,x[301000],g[301000],dp[301000]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%d",&x[i]); for(int i=m+1;i<=n;i++) { dp[i]=max(dp[i-1],g[i-m]+x[i]); g[i]=max(g[i-1],dp[i]); } printf("%d\n",g[n]); }
J Word Clock
K You Are Fired!
Code:zz
Thinking:zz
题意:解雇最少的员工,使得至少节省d元,但最多解雇k个员工。
题解:按工资从大到小排序,优先解雇工资高的。
#include<bits/stdc++.h> #define clr(a,b) memset(a,b,sizeof(a)) using namespace std; typedef long long ll; const int maxn=110010; struct s { char c[20]; long long v; }z[10010]; inline bool comp(s a,s b) { return a.v > b.v; } int main(void) { int n,k,i; long long d,zz; while(~scanf("%d %lld %d",&n,&d,&k)) { for(i = 0;i < n;i++) { scanf("%s %lld",z[i].c,&z[i].v); } sort(z,z + n,comp); zz = 0; int j; for(i = 0;i < k;i++) { zz += z[i].v; if(zz >= d) { break; } } if(zz >= d) { printf("%d\n",i + 1); for(j = 0;j <= i;j++) { printf("%s, YOU ARE FIRED!\n",z[j].c); } } else { printf("impossible\n"); } } }
赛后总结:
kk:今天被C题搞死了,想出ac想法的时候只有一点点人ac这道题,真正ac的时候却已经是四个小时后了,而且是一个小bug。不但自己没做其他题,还拖累了队友的做题速度,其实还有其他很多可做的题目,一定要多看题!以后遇到这样的情况,要勇于换人重写代码。
zz:今天就做了三道水题,读了题面就知道怎么做了,后来和队友一起搞C,其它题基本没怎么想。