AtCoder练习

1. 3721 Smuggling Marbles

大意: 给定$n+1$节点树, $0$为根节点, 初始在一些节点放一个石子, 然后按顺序进行如下操作.

  • 若$0$节点有石子, 则移入盒子
  • 所有石子移向父亲节点
  • 把所有不少于$2$个石子的节点的石子丢掉
  • 若树上还有石子,则返回第一步

对于所有$2^{n+1}$种初始放石子的方案, 求出最后盒子中石子总数之和. 

长链剖分, 这道以后再写

 2. 3727 Prefix-free Game

两个串$s,t$合法要满足 $s$不为$t$的前缀且$t$不为$s$的前缀.

一个字符串集合合法要求满足 每个串长度范围$[1,L]$, 每个串只由$01$组成, 任意两串合法.

给定合法字符串集$S$, 两人轮流操作, 每次添加一个字符串, 要求添加后$S$仍然合法, 不能操作则输. 求最后胜负情况.

假设初始$S$为空的情况. 那么初始状态可以看做两棵深度为$L$的满二叉树(因为不能取空串).

每步操作相当于选一个节点$x$, 然后删去$x$的子树以及$x$到根的链. 可以发现删完一定分裂成多颗满二叉树, 所以这样状态就只与二叉树的深度有关, 可以得到

$$SG_{x}=mex\{0,SG_{x-1},SG_{x-1}\oplus SG_{x-2},...,SG_{x-1}\oplus ...\oplus SG_{1}\}$$

打表可以发现$SG_{x}=lowbit(x)$.

所以对于给定初始字符串集合$S$的情况, 用$trie$模拟求出初始$SG$值即可.

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int n, T, tot;
ll L, ans;
struct {int ch[2];} tr[N<<2];
char s[N];

void add(int &o, char *s) {
    if (!o) o = ++tot;
    if (*s) add(tr[o].ch[*s=='1'],s+1);
}
void dfs(int o, ll d) {
    if (!o) ans ^= d&-d;
    else dfs(tr[o].ch[0],d-1),dfs(tr[o].ch[1],d-1);
}

int main() {
    scanf("%d%lld", &n, &L);
    REP(i,1,n) scanf("%s", s),add(T,s);
    dfs(T,L+1);
    puts(ans?"Alice":"Bob");
}
View Code 

3. 3939 Strange Nim

大意: $n$堆石子, 第$i$堆初始$A_i$, 有一个系数$K_i$, 每次操作假设第$i$堆有$X$个石子, 那么可以拿走的石子范围为$[1,\lfloor\frac{X}{K_i}\rfloor]$. 两人轮流操作, 求最后胜负情况.

打表可以发现$x\%k==0$时$, sg(x,k)=\lfloor\frac{x}{k}\rfloor$.

否则$sg(x,k)=sg(x-\lceil\frac{x}{k}\rceil,k)$.

通过同时减去相同的$\lceil\frac{x}{k}\rceil$来优化, 复杂度就为$O(\sqrt{k})$ 

#include <iostream>
#include <cstdio>
using namespace std;

int sg(int x, int k) {
    if (x%k==0) return x/k;
    int t = x/k+1;
    return sg(x-(x%k+t-1)/t*t,k);
}

int main() {
    int n;
    scanf("%d", &n);
    int ans = 0;
    while (n--) {
        int a, k;
        scanf("%d%d", &a, &k);
        ans ^= sg(a,k);
    }
    puts(ans?"Takahashi":"Aoki");
}
View Code

4. 2044 Teleporter

大意: $n$个点, 点$i$后继为$a_i$, 每个点都可以到达$1$, 求最少修改多少后继使得每个点恰好走$k$步能到达点$1$.

$a_1$必须为$1$, 否则$1$和$a_1$一定不能满足条件, 然后$dfs$从叶子往上贪心.

#include <iostream>
#include <cstdio>
#include <queue>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;

const int N = 1e6+10;
int n, k, ans, a[N], f[N];
vector<int> g[N];

void dfs(int x) { 
    f[x] = 1;
    for (int y:g[x]) dfs(y), f[x] = max(f[x], f[y]+1);
    if (f[x]==k&&a[x]!=1) ++ans,f[x]=0;
}
int main() {
    scanf("%d%d", &n, &k);
    REP(i,1,n) scanf("%d", a+i);
    ans = a[1]!=1;
    a[1] = 1;
    REP(i,2,n) g[a[i]].pb(i);
    dfs(1);
    printf("%d\n", ans);
}
View Code

5. 2000 Leftmost Ball

大意: 给定$n$个颜色的球, 每种$k$个, 任意排列后将每种球第一个颜色染为$0$, 求能得到多少种序列.

设$f_{i,j}$为当前放了$i$个$0$, $j$种颜色的方案数.

从左到右枚举最前面的空位应该放白球还是放彩球, 若放彩球则将剩余彩球直接分配下去.

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define PER(i,a,n) for(int i=n;i>=a;--i)
using namespace std;
typedef long long ll;
const int P = 1e9+7;
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
const int N = 2010, M = 4e6+10;
int n, k, dp[N][N];
int fac[M], ifac[M];
int C(int n, int m) {
    if (n<m) return 0;
    return (ll)fac[n]*ifac[m]%P*ifac[n-m]%P;
}
int main() {
    fac[0]=1;
    REP(i,1,M-1) fac[i]=(ll)fac[i-1]*i%P;
    ifac[M-1]=inv(fac[M-1]);
    PER(i,0,M-2) ifac[i]=(ll)ifac[i+1]*(i+1)%P;
    scanf("%d%d", &n, &k);
    if (k==1) return puts("1"),0;
    dp[0][0] = 1;
    REP(i,0,n) REP(j,0,i) {
        dp[i+1][j] = (dp[i+1][j]+dp[i][j])%P;
        dp[i][j+1] = (dp[i][j+1]+(ll)C(n*k-j*(k-1)-i-1,k-2)*dp[i][j])%P;
    }
    int ans = (ll)dp[n][n]*fac[n]%P;
    printf("%d\n", ans);
}
View Code

6. 2020 Unbalanced

大意: 若一个串满足长度不少于$2$且超过一半的字符相同, 则称它为不平衡串. 给定串$s$, 要求输出$s$的任意一个不平衡子串.

众数的套路题. 枚举字符$x$作为众数的情况, $x$看做$1$, 其余字符看做$-1$, 那么就等价于找一个和大于零的区间.

#include <iostream>
#include <cstdio>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define x first
#define y second
using namespace std;
typedef pair<int,int> pii;

const int N = 1e6+10;
int n, f[N][30];
pii mi[30];
char s[N];

int main() {
    scanf("%s", s+1);
    n = strlen(s+1);
    REP(i,1,n) {
        memcpy(f[i],f[i-1],sizeof f[0]);
        REP(j,'a','z') {
            if (s[i]==j) ++f[i][j-'a'];
            else --f[i][j-'a'];
        }
        if (i>1) { 
            REP(j,0,25) if (f[i][j]-mi[j].x>0) { 
                return printf("%d %d\n",mi[j].y+1,i),0;
            }
        }
        REP(j,0,25) mi[j] = min(mi[j], pii(f[i-1][j],i-1));
    }
    puts("-1 -1");
}
View Code

看了其他人题解发现有更简便做法, 因为只需要找一个, 所以直接判断是否存在$XYX$或$XX$这种即可. 

7. 2021 Children and Candies

大意: $n$个人分$C$块糖. 定义函数$f(x_1,...,x_n)$, 第$i$个人若分$a$块糖, 则高兴度为$x_i^a$, $f$的值为所有人高兴度的乘积. 给定序列$A,B$, 求$\sum\limits_{x_1=A_1}^{B_1}\sum\limits_{x_2=A_2}^{B_2}\cdots\sum\limits_{x_n=A_n}^{B_n}f(x_1,x_2,...,x_n)$

简单dp题, 设$dp_{i,x}$为前$i$个人分$x$块糖的答案, 可以得到$dp_{i,x}=\sum\limits_{A_i\le k\le B_i}\sum\limits_{y\le x}dp_{i-1,y}k^{x-y}$.

然后前缀优化一下.

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int P = 1e9+7, INF = 0x3f3f3f3f;
const int N = 410;
int n, c, a[N], b[N], dp[N][N];
int po[N][N], sum[N][N];

int main() {
    REP(i,1,N-1) { 
        po[i][0] = 1;
        REP(j,1,N-1) po[i][j] = (ll)po[i][j-1]*i%P;
    }
    REP(i,0,N-1) { 
        sum[i][0] = 1;
        REP(j,1,N-1) sum[i][j] = (sum[i][j-1]+po[j][i])%P;
    }
    cin>>n>>c;
    REP(i,1,n) cin>>a[i];
    REP(i,1,n) cin>>b[i];
    dp[0][0] = 1;
    REP(i,1,n) REP(x,0,c) REP(y,0,x) {
        int ret = sum[x-y][b[i]]-sum[x-y][a[i]-1];
        dp[i][x] = (dp[i][x]+(ll)dp[i-1][y]*ret)%P;
    }
    int ans = dp[n][c];
    if (ans<0) ans += P;
    printf("%d\n", ans);
}
View Code

8. 2022  Unhappy Hacking

大意: 键盘有三个键$0,1$和退格, 求按$n$次以后得到字符串$s$的方案数

显然答案只与$n$有关, 所以求出能得到的所有串的方案数最后除以$2^{|s|}$即可

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int P = 1e9+7;
ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;}
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
const int N = 5010;
int n, m, dp[N][N];
char s[N];
void add(int &x, ll y) {x = (x+y)%P;}

int main() {
    scanf("%d%s", &n, s+1);
    m = strlen(s+1);
    dp[0][0] = 1;
    REP(i,0,n) REP(j,0,i) if (dp[i][j]) {
        add(dp[i+1][j+1],2*dp[i][j]);
        add(dp[i+1][max(j-1,0)],dp[i][j]);
    }
    int ans = (ll)dp[n][m]*inv(qpow(2,m))%P;
    printf("%d\n", ans);
}
View Code

9. 2070 Card Game for Three

大意: $A,B,C$三个人初始$n,m,k$张牌, 每张牌上是三个人名字. 每个人出牌顺序固定,  每轮出一张牌, 然后牌上写的人接着出. 谁先出完谁赢. 对于所有$3^{n+m+k}$中出牌顺序, 求先手胜利方案数.

显然对于一个长度为$x$的出牌序列, 对应$3^{n+m+k-x}$种方案.

只需要考虑出$n$张$A$,$i$张$B$,$j$张$C$, 且最后一张为$A$的方案数, 有

$$\begin{align} ans &=\sum\limits_{i=0}^m\sum\limits_{j=0}^k 3^{m+k-i-j}\frac{(n+i+j-1)!}{(n-1)!i!j!} \notag \\ &= \frac{3^{m+k}}{(n-1)!}\sum\limits_{i=0}^m\frac{3^{-i}}{i!}\sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)! \notag \end{align}$$

记$f(i)=\sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)!$

$$\begin{align} f(i+1)-f(i) &= \sum\limits_{j=0}^k\frac{3^{-j}}{j!}(n+i+j-1)!(n+i+j-1) \notag \\ &= f(i)(n+i-1)+\sum\limits_{j=0}^k \frac{3^{-j}j}{j!}(n+i+j-1)! \notag \\ &= f(i)(n+i-1)+3^{-1}(f(i+1)-\frac{3^{-k}}{k!}(n+i+k)!) \notag \end{align}$$

所以

$$f(i+1)=\frac{3}{2}f(i)(n+i)-\frac{3^{-k}}{2}\frac{(n+i+k)!}{k!}$$

然后就可以$O(n)$做了

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define PER(i,a,n) for(int i=n;i>=a;--i)
using namespace std;
typedef long long ll;
const int N = 1e6+10, P = 1e9+7, inv2 = (P+1)/2;
int inv(int x){return x<=1?1:inv(P%x)*(P-(ll)P/x)%P;}
int n,m,k,fac[N],ifac[N],po[N],ipo[N];
int main() {
    ifac[0]=fac[0]=po[0]=ipo[0]=1;
    REP(i,1,N-1) { 
        fac[i]=fac[i-1]*(ll)i%P;
        po[i]=po[i-1]*3ll%P;
    }
    ifac[N-1]=inv(fac[N-1]),ipo[N-1]=inv(po[N-1]);
    PER(i,1,N-2) { 
        ifac[i]=ifac[i+1]*(i+1ll)%P;
        ipo[i]=ipo[i+1]*3ll%P;
    }
    cin>>n>>m>>k;
    int ans = 0, ret = 0;
    REP(j,0,k) ret = (ret+(ll)ipo[j]*ifac[j]%P*fac[n+j-1])%P;
    REP(i,0,m) { 
        ans = (ans+(ll)ipo[i]*ifac[i]%P*ret)%P;
        ret = 3ll*inv2%P*ret%P*(n+i)%P;
        ret = (ret-(ll)ipo[k]*inv2%P*fac[n+i+k]%P*ifac[k])%P;
    }
    ans = (ll)ans*po[m+k]%P*ifac[n-1]%P;
    if (ans<0) ans += P;
    printf("%d\n", ans);
}
View Code

10. 4257 Factorization

大意: 给定$n,m$, 求长为$n$的序列,乘积等于$m$的方案数.

记$f_{i,x}$为$i$个数乘积$x$的方案, 就有$f_{i,x}=\sum\limits_{ab=x}f_{i-1,a}f_{1,b}$  

所以$f$就为积性函数, 有$f_{n,p^k}=\binom{n+k-1}{k}$, 然后相乘即可.

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define PER(i,a,n) for(int i=n;i>=a;--i)
using namespace std;
typedef long long ll;
const int P = 1e9+7, INF = 0x3f3f3f3f;
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
const int N = 1e6+10;
int n, m, fac[N], ifac[N];
int C(int n, int m) {
    if (n<m) return 0;
    return fac[n]*(ll)ifac[m]%P*ifac[n-m]%P;
}
int main() {
    fac[0]=1;
    REP(i,1,N-1) fac[i]=(ll)fac[i-1]*i%P;
    ifac[N-1]=inv(fac[N-1]);
    PER(i,0,N-2) ifac[i]=(ll)ifac[i+1]*(i+1)%P;
    scanf("%d%d", &n, &m);
    int ans = 1;
    for (int i=2; i*i<=m; ++i) {
        int cnt = 0;
        while (m%i==0) m/=i,++cnt;
        ans = (ll)ans*C(n+cnt-1,cnt)%P;
    }
    if (m>1) ans = (ll)ans*n%P;
    printf("%d\n", ans);
}
View Code

11. 3606 Combination Lock

大意:给定字符串$S$, $n$种操作$(L,R)$, 将$s[L...R]$字符加$1$, $z$变为$a$. 每种操作可以执行任意次, 可以按任意顺序执行, 求能否变为回文串.

区间加可以差分为$++c[l],--c[r+1]$, 回文限制相当于所有对称位置的差分之和为$0$.

连边看每个连通块的和是否为$0$即可.

#include <iostream>
#include <cstdio>
#include <queue>
#include <string.h>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define pb push_back
using namespace std;
const int N = 1e6+10;
int n, m, sum, c[N], vis[N];
char s[N];
vector<int> g[N];
void add(int x, int y) {
    g[x].pb(y),g[y].pb(x);
}
void dfs(int x) {
    if (vis[x]) return;
    vis[x] = 1;
    sum = (sum+c[x])%26;
    for (int y:g[x]) dfs(y);
}
int main() {
    cin>>s+1;
    m = strlen(s+1);
    REP(i,1,m+1) { 
        c[i]=(s[i]-s[i-1])%26;
        add(i,m+2-i);
    }
    cin>>n;
    while (n--) {
        int l,r;
        cin>>l>>r;
        add(l,r+1);
    }
    REP(i,1,m+1) {
        sum = 0;
        dfs(i);
        if (sum) return puts("NO"),0;
    }
    puts("YES");
}
View Code

12. 3605 Zabuton

大意: $n$个人, 初始高度为$0$, 若当前高度不超过$h_i$, 那么第$i$个人可以叠上去, 使高度增加$p_i$, 求最多能叠多少个人.

贪心按$h+p$排序, 然后$dp$一定最优, 考虑证明.

对于两个人$(h_a,p_a),(h_b,p_b)$, 假设$a,b$之前的和为$x$.

若$a$排在$b$前, 有$x\le min(h_a,h_b-p_a)$, 否则有$x\le min(h_b,h_a-p_b)$.

$a$在前比$b$在前更优等价于$min(h_a,h_b-p_a)>min(h_b,h_a-p_b)$

去掉$min$可以得到$h_a+p_a<h_b+p_b$.

#include <iostream>
#include <sstream>
#include <algorithm>
#include <cstdio>
#include <math.h>
#include <set>
#include <map>
#include <queue>
#include <string>
#include <string.h>
#include <bitset>
#define REP(i,a,n) for(int i=a;i<=n;++i)
#define PER(i,a,n) for(int i=n;i>=a;--i)
#define hr putchar(10)
#define pb push_back
#define lc (o<<1)
#define rc (lc|1)
#define mid ((l+r)>>1)
#define ls lc,l,mid
#define rs rc,mid+1,r
#define x first
#define y second
#define io std::ios::sync_with_stdio(false)
#define endl '\n'
#define DB(a) ({REP(__i,1,n) cout<<a[__i]<<' ';hr;})
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int P = 1e9+7, INF = 0x3f3f3f3f;
ll gcd(ll a,ll b) {return b?gcd(b,a%b):a;}
ll qpow(ll a,ll n) {ll r=1%P;for (a%=P;n;a=a*a%P,n>>=1)if(n&1)r=r*a%P;return r;}
ll inv(ll x){return x<=1?1:inv(P%x)*(P-P/x)%P;}
inline int rd() {int x=0;char p=getchar();while(p<'0'||p>'9')p=getchar();while(p>='0'&&p<='9')x=x*10+p-'0',p=getchar();return x;}
//head



const int N = 5e3+10;
int n;
ll dp[N];
struct _ {
    int x,y;
    bool operator < (const _ &rhs) const {
        return x+y<rhs.x+rhs.y;
    }
} a[N];

int main() {
    cin>>n;
    REP(i,1,n) cin>>a[i].x>>a[i].y;
    sort(a+1,1+a+n);
    memset(dp,0x3f,sizeof dp);
    dp[0] = 0;
    REP(i,1,n) {
        PER(j,0,i-1) if (dp[j]<=a[i].x) {
            dp[j+1]=min(dp[j+1],dp[j]+a[i].y);
        }
    }
    PER(i,0,n) if (dp[i]<=1e16) return printf("%d\n",i),0;
}
View Code

13. 2292 Division into Two

大意: 给定序列, 求划分为两个集合$X,Y$, 满足$X$中任意两数之差的绝对值不少于$A$, $Y$中任意两数之差的绝对值不少于$B$, 求方案数.

$DP$好题.

首先$O(n^2)$的$DP$很容易想, 只要枚举上次出现位置即可. 

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int P = 1e9+7, INF = 0x3f3f3f3f;
const int N = 1e3+10;
int n, dp[2][2][N];
ll a,b,s[N];

void add(int &x, int y) {x+=y;if (x>=P)x-=P;}
int main() {
    cin>>n>>a>>b;
    REP(i,1,n) cin>>s[i];
    s[0] = -1e18;
    int cur = 0, ans = 0;
    dp[0][0][0] = 1;
    //dp[i][z][j]
    //z为0, X上个数位置在i, Y上个数位置在j
    //z为1, X上个数位置在j, Y上个数位置在i
    REP(i,1,n) {
        cur ^= 1;
        memset(dp[cur],0,sizeof dp[cur]);
        REP(j,0,i-1) REP(z,0,1) {
            int &r = dp[!cur][z][j];
            if (!r) continue;
            if (!z&&s[i]-s[i-1]>=a||z&&s[i]-s[i-1]>=b) {
                add(dp[cur][z][j],r);
                if (i==n) add(ans,r);
            }
            if (!z&&s[i]-s[j]>=b||z&&s[i]-s[j]>=a) {
                add(dp[cur][!z][i-1],r);
                if (i==n) add(ans,r);
            }
        }
    }
    printf("%d\n", ans);
}
View Code

考虑$O(n)$的做法, 记$dp_i$为集合$Y$取第$i$个数的方案数, 可转移的$j$要满足

$$s_i-s_j\ge B$$

$$s_k-s_{k-1}\ge A,k\in [j+2,i-1]$$

所以$j$是一段连续的区间, 可以前缀和优化一下即可$O(n)$

#include <iostream>
#include <cstdio>
#define REP(i,a,n) for(int i=a;i<=n;++i)
using namespace std;
typedef long long ll;
const int P = 1e9+7;
const int N = 1e6+10;
int n,f[N];
ll s[N],a,b;

int main() {
    cin>>n>>a>>b;
    if (a>b) swap(a,b);
    REP(i,1,n) cin>>s[i];
    REP(i,3,n) if (s[i]-s[i-2]<a) return puts("0"),0;
    f[0] = 1;
    int l=0, r=0;
    s[n+1] = 2e18;
    REP(i,1,n+1) {
        while (r<i-1&&s[i]-s[r+1]>=b) ++r;
        if (l<=r) f[i]=(f[r]-(l?f[l-1]:0))%P;
        f[i] = (f[i]+f[i-1])%P;
        if (i>1&&s[i]-s[i-1]<a) l=i-1;
    }
    int ans = (f[n+1]-f[n])%P;
    if (ans<0) ans+=P;
    printf("%d\n",ans);
}
View Code

 

 

 

转载于:https://www.cnblogs.com/uid001/p/11198317.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值