2017-2018 ACM-ICPC German Collegiate Programming Contest (GCPC 2017)
作者:互联网
Solved
- B、Buildings
- C、Joyride
- D、Pants On Fire
- E、Perpetuum Mobile
- F、Plug It In!
- G、Water Testing
- I、Überwatch
- K、You Are Fired!
B、Buildings
题意:
有 \(m\) 个面,每面有 \(n\times n\) 个放个,你有 \(c\) 种颜色去涂,旋转重合算一种方案。问有多少种涂色方案。
想法:
\(polya\) 模版题
代码:
ll qfast(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1) ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans%mod;
}
void run()
{
ll n=rdll(),m=rdll(),c=rdll();
ll x=qfast(c,n*n);
ll ans=0;
for(ll i=1;i<=m;i++)
{
ans+=qfast(x,__gcd(i,m));
ans%=mod;
}
ans*=qfast(m,mod-2);
printf("%lld\n",ans%mod);
}
signed main()
{
// int t=rd();
// while(t--)
run();
return 0;
}
C、Joyride
题意:
带小孩去公园玩,从 \(1\) 号点开始,公园里每个项目都有游玩时间 \(t\) 和游玩花费 \(w\) ,并且游玩不能中断,每个项目之间会有一些路径相通,每条路径需要 \(T\) 时间通过。并且在时刻 \(x\),必须让小孩回到 \(1\) 号点。如果经过一个游玩项目就必须游玩,问是否有可能在 \(x\)时刻正好在起始点,有可能的话就求出最小花费。
想法:
用 \(dist[i][j]\) 表示在 \(i\) 点,时间为 \(j\) 时的最小花费。然后建图去跑最短路即可。
代码:
int x,n,m,s,t,T;
int tt[MAXN],pp[MAXN];
struct qnode{
int v;
ll t,c;
qnode(int _v=0,ll _t=0,ll _c=0):v(_v),t(_t),c(_c){}
bool operator <(const qnode &r)const{
return c>r.c;
}
};
vector<int>E[MAXN];
bool vis[2005][2005];
ll dist[2005][2005]; //在第i点,时间为j时的最小花费
void Dijkstra(){
for(int i=1;i<=2000;i++){
for(int j=1;j<=2000;j++){
dist[i][j]=INF;
}
}
mem(vis,false);
priority_queue<qnode>que;
while(!que.empty())que.pop();
dist[1][tt[1]]=pp[1];
//vis[1][tt[1]]=1;
que.push(qnode(1,tt[1],pp[1]));
qnode tmp;
while(!que.empty()){
tmp=que.top();
que.pop();
int var=tmp.v;
ll cost=tmp.c;
ll time=tmp.t;
//printf("ver: %d time: %lld cost: %lld\n", var, time, cost);
if(vis[var][time])continue;
vis[var][time]=1;
if(time+tt[var]<=x&&cost+pp[var]<dist[var][time+tt[var]]){
//cout<<cost+pp[var]<<endl;
dist[var][time+tt[var]]=cost+pp[var];
que.push(qnode(var,time+tt[var],cost+pp[var]));
}
for(int i=0;i<E[var].size();i++){
int to=E[var][i];
if(time+T+tt[to]<=x&&cost+pp[to]+0ll<dist[to][time+T+tt[to]]){
dist[to][time+T+tt[to]]=cost+pp[to];
que.push(qnode(to,time+T+tt[to],cost+pp[to]));
}
}
}
}
int main()
{
scanf("%d",&x);
scanf("%d%d%d",&n,&m,&T);
for(int i=1;i<=m;i++){
int a,b;
scanf("%d%d",&a,&b);
E[a].push_back(b);
E[b].push_back(a);
}
for(int i=1;i<=n;i++){
scanf("%d%d",&tt[i],&pp[i]);
}
Dijkstra();
if(dist[1][x]==1e18+5||x<tt[1]){
printf("It is a trap.");
}else{
printf("%lld",dist[1][x]);
}
return 0;
}
D、Pants On Fire
想法:
每个人的关系去建图,通过 \(Floyd\) ,使用传递闭包处理。
代码:
map<string,int>mp;
int mm[404][404];
int n,m;
int ans;
void Floyd()
{
for(int k=1;k<=ans;k++)
{
for(int i=1;i<=ans;i++)
{
for(int j=1;j<=ans;j++)
{
mm[i][j] = min(mm[i][j],mm[i][k]+mm[k][j]);
}
}
}
}
int main()
{
cin>>n>>m;
ans = 1;
mp.clear();
for(int i=1;i<=2*n;i++)
{
for(int j=1;j<=2*n;j++)
{
if(i!=j) mm[i][j] = INF;
}
}
for(int i=1;i<=n;i++)
{
string a,b,c,d,e;
cin>>a>>b>>c>>e>>d;
if(!mp[a])
{
//cout<<a<<endl;
mp[a] = ans;
ans++;
}
if(!mp[d])
{
//cout<<d<<endl;
mp[d] = ans;
ans++;
}
mm[mp[a]][mp[d]] = 1;
}
Floyd();
/*map<string,int>::iterator it;
for(it=mp.begin();it!=mp.end();it++)
{
cout<<it->first<<" "<<it->second<<endl;
}*/
//cout<<mm[3][1]<<mm[1][2]<<endl;
while(m--)
{
string a,b,c,d,e;
cin>>a>>b>>c>>e>>d;
if(mp[a]==0||mp[d]==0)
{
puts("Pants on Fire");
continue;
}
//cout<<mp[a]<<" "<<mp[d]<<endl;
//cout<<mm[mp[a]][mp[d]]<<" "<<mm[mp[d]][mp[a]]<<endl;
if(mm[mp[a]][mp[d]]==INF&&mm[mp[d]][mp[a]]==INF)
{
puts("Pants on Fire");
}
else if(mm[mp[a]][mp[d]]!=INF&&mm[mp[d]][mp[a]]==INF)
{
puts("Fact");
}
else{
puts("Alternative Fact");
}
}
}
E、Perpetuum Mobile
题意:
给出一副有向图,问图上是否存在环,其环上的边权乘积大于 \(1\)。
想法:
因为边权是浮点数,且需要进行乘法运算,所以我们给每个边权加一个 \(log\),把乘法运算变成加法运算。最后我们可以通过 \(spfa\) 来判断是否存在正环,并且此时 \(spfa\) 中需要求的是最长路。
代码:
const int NUM=4005;
struct edge{
int to,next;
double w;
edge(int a,int b,double c){to=a;next=b;w=c;}
};
vector<edge>e[NUM];
int n,m;
/*
int pre[NUM];
void print_path(int s,int t)
{
if(s==t)printf("%d",s);
print_path(s,pre[t]);
printf("%d",t);
}
*/
bool spfa()
{
double dis[NUM];
bool inq[NUM];
int neg[NUM];
for(int i=1;i<=n;i++){dis[i]=-1000000;inq[i]=false;neg[i]=0;}
queue<int>Q;
for(int i=1;i<=n;i++){
Q.push(i);
inq[i]=true;
}
while(!Q.empty())
{
int u=Q.front();
Q.pop();
inq[u]=false;
for(int i=0;i<e[u].size();i++){
int v=e[u][i].next;
double w=e[u][i].w;
if(dis[v]<dis[u]+w){
dis[v]=dis[u]+w;
//pre[v]=u;
if(!inq[v]){
Q.push(v);
inq[v]=true;
neg[v]++;
if(neg[v]>=n){return true;}
}
}
}
}
//printf("%d\n",dis[n]);
return false;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b;
double c;
scanf("%d %d %lf",&a,&b,&c);
e[a].push_back(edge(a,b,log(c)));
}
if(!spfa()){
printf("admissible\n");
}else{
printf("inadmissible\n");
}
return 0;
}
F、Plug It In!
题意:
有 \(n\) 中插头, \(m\) 个电, \(k\) 种电器和插头匹配方案,你可以选择一种插座将他的数量从1变为3,问最多可以有多少个电器可以匹配插头。
想法:
- 考虑没有增加插头时的匹配方案,即一次二分图匹配即可。
- 增加插头,那么如果枚举增加插头种类再进行二分图匹配,时间复杂度 \(O(n^3)\) ,会超时。
- 想到之前已经匹配好的可以作为重新匹配的图,每次重新匹配在原来已经匹配好的图上加入两个新点,那么每次重新匹配时只需要匹配两个点即可。
代码:
int G[MAXN][MAXN];
int match[MAXN],reserve_boy[MAXN],match2[MAXN];
int k,m,n;
bool dfs(int x)
{
for(int i=1;i<=n;i++){
if(!reserve_boy[i]&&G[x][i]){
reserve_boy[i]=1;
if(!match[i]||dfs(match[i])){
match[i]=x;
return true;
}
}
}
return false;
}
bool dfs2(int x)
{
for(int i=1;i<=n;i++){
if(!reserve_boy[i]&&G[x][i]){
reserve_boy[i]=1;
if(!match2[i]||dfs2(match2[i])){
match2[i]=x;
return true;
}
}
}
return false;
}
vector<int>g[MAXN];
int main()
{
scanf("%d%d%d",&m,&n,&k);
memset(G,0,sizeof G);
memset(match,0,sizeof match);
for(int i=0;i<k;i++){
int a,b;
scanf("%d %d",&a,&b);
g[a].push_back(b);
G[a][b]=1;
}
int sum=0;
for(int i=1;i<=m;i++){
memset(reserve_boy,0,sizeof reserve_boy);
if(dfs(i))sum++;
}
/*
for(int i=1;i<=n;i++){
printf("%d\n",match[i]);
}
*/
int maxx=sum,ans;
for(int i=1;i<=m;i++){
ans=0;
mem(match2,0);
for(int j=1;j<=n;j++){
match2[j]=match[j];
}
for(int j=0;j<g[i].size();j++){
G[m+1][g[i][j]]=1;
G[m+2][g[i][j]]=1;
}
ans=sum;
for(int j=m+1;j<=m+2;j++){
memset(reserve_boy,0,sizeof reserve_boy);
if(dfs2(j))ans++;
}
maxx=max(maxx,ans);
for(int j=0;j<g[i].size();j++){
G[m+1][g[i][j]]=0;
G[m+2][g[i][j]]=0;
}
}
printf("%d\n",maxx);
return 0;
}
G、Water Testing
题意:
按顺序给出 \(n\) 个点,问由这 \(n\) 个点构成的多边形内部有多少个整数点。
想法:
-
\(pick\) 定理:\(S = n+\frac{x}{2}-1\),其中 \(S\)=多边形面积,\(n\)=多边形内部整数点\(,\)x$=多边形的边上的整数点。
-
多边形面积:\(S = \frac{1}{2}abs( x_{1}\times y_{2}-x_{2}\times y_{1}+......+x_{n-1}\times y_{n}-x_{n}\times y_{n-1}+x_{n}\times y_{1}-x_{1}\times y_{n}\)
-
边上整数点:\(x = gcd(x_{1}-x_{2},y_{1}-y_{2})+......+gcd(x_{n-1}-x_{n},y_{n-1}-y_{n})+gcd(x_{n}-x_{1},y_{n}-y_{1})\)
代码:
ll gcd(ll a,ll b)
{
if(b == 0)
return a;
return gcd(b,a%b);
}
ll a[maxn],b[maxn];
int n;
int main()
{
int n;
cin >> n;
ll x0,y0,x,y,S = 0;
cin >> x >> y;
x0 = x;
y0 = y;
a[1] = x;
b[1] = y;
for(int i=2;i<=n;i++) {
ll xtmp,ytmp;
cin >> xtmp >> ytmp;
a[i] = xtmp;
b[i] = ytmp;
S += (x*ytmp-y*xtmp);
x = xtmp;
y = ytmp;
}
S += (x*y0-y*x0);
ll sum = 0;
// cout<<S<<endl;
for(int i=1;i<=n;i++){
if(i<n){
ll aa=abs(a[i+1]-a[i]);
ll bb=abs(b[i+1]-b[i]);
sum+=gcd(aa,bb)-1;
}else{
ll aa=abs(a[1]-a[n]);
ll bb=abs(b[1]-b[n]);
sum+=gcd(aa,bb)-1;
}
}
sum+=n;
printf("%lld\n",abs(S)/2+1-sum/2);
}
I、Überwatch
想法:
简单dp,转移方程为:
代码:
int dp[maxn];
int a[maxn];
int main()
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int ans=0;
for(int i=m+1;i<=n;i++){
dp[i]=dp[i-1];
dp[i]=max(dp[i-m]+a[i],dp[i]);
}
cout<<dp[n];
}
K、You Are Fired!
water
代码:
struct node{
string s;
ll num;
}p[10005];
bool cmp(node a,node b){
return a.num>b.num;
}
vector<string>pp;
int main()
{
ll n,d,k;
pp.clear();
scanf("%lld%lld%lld",&n,&d,&k);
for(int i=1;i<=n;i++){
cin>>p[i].s;
scanf("%lld",&p[i].num);
}
sort(p+1,p+n+1,cmp);
ll sum=0;
for(int i=1;i<=k;i++){
sum+=p[i].num;
pp.push_back(p[i].s);
if(sum>=d)break;
}
if(sum<d){
printf("impossible");
}else{
cout<<pp.size()<<endl;
for(int i=0;i<pp.size();i++){
cout<<pp[i]<<", YOU ARE FIRED!"<<endl;
}
}
}
标签:匹配,Contest,German,ll,times,int,MAXN,2017,dp 来源: https://www.cnblogs.com/ha-chuochuo/p/14353633.html