2017-2018 ACM-ICPC German Collegiate Programming Contest (GCPC 2017)

Solved


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,转移方程为:

$dp[i]=dp[i-1]$ (不选)
$dp[i]=max(dp[i],dp[i-m]+a[i])$ (选了)

代码:

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;
      }
    }
}
上一篇:4.Silverlight 4.0添加鼠标右键菜单和Silverlight全屏模式的进入退出。


下一篇:Samara Farewell Contest 2020 (XXI Open Cup, GP of Samara) D. Two Pirates - 2