AGC002 补题小结

Problem A Range Product

简要题意:给定 \(a,b\) 两个数字,输出 \(\prod_{i=a}^b i\) 是正数,负数还是零。 \(-10^9 \le a\le b\le 10^9\)

tag:分类讨论

题解:分类讨论一下没了

#include <cstdio>
int main (){
    int a,b;scanf ("%d%d",&a,&b);
    if (a<=0&&b>=0) puts("Zero");
    else if (a<0&&b<0&&((b-a+1)&1)) puts("Negative");
    else puts("Positive");
    return 0;
}

Problem B Box and Ball

简要题意:\(N\) 个盒子,第一个盒子里是个红球,其他 \(N-1\) 个盒子里都是白球。现在给出 \(M\) 次操作,操作的方式是在一个盒子中随机取出一个球放入另一个盒子中。在 \(M\) 次操作完后,求出红球可能在的盒子的总数。\(N,M \le 10^5\)

tag:枚举,按题意模拟

题解:按题意模拟,记录每个盒子是否可能有球

#include <cstdio>
const int N=100005;
bool vis[N];int cnt[N];
int main (){
    int n,m;scanf ("%d%d",&n,&m);vis[1]=true;
    for (int i=1;i<=n;i++) cnt[i]=1;
    for (int i=1,x,y;i<=m;i++){
        scanf ("%d%d",&x,&y);
        vis[y]|=vis[x],cnt[x]--,cnt[y]++;
        if (cnt[x]==0) vis[x]=false;
    }
    int ans=0;for (int i=1;i<=n;i++) ans+=vis[i];
    printf ("%d",ans);
    return 0;
}

Problem C Knot Puzzle

简要题意:有 \(N\) 条绳子,第 \(i\) 条的长度是 \(A_i\) 。 相邻两条绳子打了结,每次选一段绳子, 如果这段绳子的总长度大于给定的变量 \(L\) ,我们可以在这段绳子中选一个解开。问是否有一种方案把所有绳子都解开。 \(N \le 10^5\)

tag:贪心

题解:考虑进行到只剩一个绳结,则两段绳子的长度和必须大于等于L,否则这个绳结没法解。所以找到整根绳子中长度和大于等于L的两段,将左边的绳结从左往右解开,右边的绳结从右往左解开,最后将这个绳结解开。

#include <cstdio>
const int N=100005;
int a[N];
int main (){
    int n,L;scanf ("%d%d",&n,&L);
    for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
    for (int i=1;i<n;i++){
        if (a[i]+a[i+1]>=L) {
            puts("Possible");
            for (int j=1;j<i;j++)  printf ("%d\n",j);
            for (int j=n-1;j>i;j--) printf ("%d\n",j);
            printf ("%d",i);
            return 0;
        }
    }
    puts("Impossible");
    return 0;
}

Problem D Stamp Rally

简要题意:一张连通图, \(q\) 次询问从两个点 \(x\)\(y\) 出发,希望经过的点(不重复)数量等于 \(z\) ,经过的边最大编号最小是多少。\(n,q \le 10^5\)

tag:整体二分,带撤销并查集

题解:\(Atcoder\) 里罕见的数据结构题,对所有询问一起二分答案,用并查集判断是否可行,挺套路的一种题。

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=1e5+5;
int ans[N];
inline int Max(int a,int b){
    return a>b?a:b;
}
struct Edge{
    int u,v;
}e[N];
struct Node{int id,x,y,z;};
int fa[N];
inline int getrt(int x){while (fa[x]!=x)x=fa[x];return x;}
int size[N],deep[N],s[N][2],top=0;
bool tag[N];
inline void link(int x,int y){
    if (deep[x]<deep[y]) swap(x,y);
    ++top,s[top][0]=x,s[top][1]=y;
    fa[y]=x,size[x]+=size[y];
    if (deep[x]==deep[y]) deep[x]++,tag[top]=1;else tag[top]=0;
}
inline void pop(){
    fa[s[top][1]]=s[top][1],size[s[top][0]]-=size[s[top][1]];
    deep[s[top][0]]-=tag[top];tag[top]=0;--top;
}
inline void work(int l,int r,vector <Node > now){
    if (now.size()==0) return;
    if (l==r){
        for (int i=0;i<now.size();i++) ans[now[i].id]=l;
        return;
    }
    vector <Node > mx,mn;mx.clear(),mn.clear();
    int mid=(l+r)>>1,nowtop=top;
    for (int i=l,x,y;i<=mid;i++) if ((x=getrt(e[i].u))!=(y=getrt(e[i].v))) link(x,y);
    for (int i=0;i<now.size();i++){
        int x=getrt(now[i].x),y=getrt(now[i].y),sum=0;
        if (x!=y) sum=size[x]+size[y];
        else sum=size[x];
        if (sum>=now[i].z) mn.push_back(now[i]);
        else mx.push_back(now[i]);
    }
    work(mid+1,r,mx);
    while (top>nowtop) pop();
    work(l,mid,mn);
}
int main (){
    int n,m,q;scanf ("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
        scanf ("%d%d",&e[i].u,&e[i].v);
    scanf ("%d",&q);
    vector <Node > t;t.clear();
    for (int i=1;i<=q;i++){
        int x,y,z;scanf ("%d%d%d",&x,&y,&z);
        t.push_back((Node){i,x,y,z});
    }
    for (int i=1;i<=n;i++) fa[i]=i,size[i]=deep[i]=1;
    work(1,m,t);
    for (int i=1;i<=q;i++) printf ("%d\n",ans[i]);
    return 0;
}

Problem E Candy Piles

简要题意:桌子上有 \(n\) 堆糖,第 \(i\) 堆有 \(a_i\) 个,两个人轮流玩游戏,有两种操作:

  1. 把最多的一堆吃完
  2. 把每一堆吃掉一个

吃完的人输,问两人足够聪明的博弈结果。 \(n\le 10^5,a_i\le 10^9\)

tag:博弈,推结论

题解:首先转化为平面上,按照 \(a_i\) 从大到小排序,两个人轮流走,可以向上或者向右走,走不了的人失败

有个结论,\((x,y)\) 这个状态如果对于先手是必胜态,那么 \((x+1,y+1)\) 也对于先手是必胜态,反之亦然

所以我们可以直接找到 \((0,0)\) 对应的是哪个点 \((i,i)\) ,算出这个点到上方和右方轮廓的距离,只要有一个是偶数,就先手必胜。

#include <cstdio>
#include <algorithm>
using namespace std;
const int N=100005;
int a[N];
bool cmp(int a,int b){return a>b;}
int main (){
    int n;scanf ("%d",&n);
    for (int i=1;i<=n;i++) scanf ("%d",&a[i]);
    sort(a+1,a+n+1,cmp);
    for (int i=1;i<=n;i++)
        if (i+1>a[i+1]){
            int j=i+1;while (a[j]==i) j++;
            if (((j-i-1)&1)|((a[i]-i)&1)) puts("First");
            else puts("Second");
            return 0;
        }
    return 0;
}

Problem F Leftmost Ball

简要题意:给你 \(n\) 种颜色的球,每个球有 \(k\) 个,把这 \(n\times k\) 个球排成一排,把每一种颜色的最左边出现的球涂成白色(初始球不包含白色),求有多少种不同的颜色序列,答案对 \(10^9+7\) 取模 。\(n\le 2000\)

tag:计数类 \(dp\) ,组合数学

题解:设 \(f[i][j]\) 表示填了 \(i\) 个白色球,\(j\) 彩色球的方案数,那么显然 \(j\le i\)

考虑这个转移,首先可以填一个白色,就是 \(f[i][j]+=f[i-1][j]\)

放新的颜色的球:即从 \(f[i][j-1]\) 转移,先在剩下 \(n-j+1\) 种没有放置过的颜色中选择一个作为这次放置的颜色,放置在第一个空位(按转移规则,已经放置的 \(i\) 个白球一定都在当前第一个空位的前面,因此必定合法)。放完后还剩 \(k-2\) 的这个颜色的球没有放,显然只需要在剩下的后面 \(n\times k-i-(j-1)\times (k-1)-1\) 个空位里找任意 \(k-2\) 个空位放就好,有 $ \binom{n\times k−(i+(j−1)\times (k−1))}{k−2}$ 种方案

所以总的转移方程是
\[ f[i][j]=f[i-1][j]+f[i][j-1]\times (n-j+1)\times \binom{n\times k−(i+(j−1)\times (k−1))}{k−2} \]
注意特判 \(k=1\) 的情况

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N=2005,Mod=1e9+7;
int dp[N][N],fac[N*N],inv[N*N];
inline int C(int n,int m){
    return 1ll*fac[n]*inv[m]%Mod*inv[n-m]%Mod;
}
inline int qpow(int a,int b){
    int ans=1;
    while (b){
        if (b&1) ans=1ll*ans*a%Mod;
        a=1ll*a*a%Mod,b>>=1;
    }
    return ans;
}
int main (){
    int n,k;scanf ("%d%d",&n,&k);
    if (k==1) {puts("1");return 0;}
    dp[0][0]=1;fac[0]=1;
    for (int i=1;i<=n*k;i++) fac[i]=1ll*fac[i-1]*i%Mod;
    inv[n*k]=qpow(fac[n*k],Mod-2);for (int i=n*k-1;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%Mod;
    for (int i=0;i<=n;i++) dp[i][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=i;j++)
            dp[i][j]=dp[i-1][j]+1ll*dp[i][j-1]*(n-j+1)%Mod*C(n*k-(i+(j-1)*(k-1))-1,k-2)%Mod,dp[i][j]%=Mod;
    printf ("%d",dp[n][n]);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/crazyzh/p/10883753.html
今日推荐