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 
#include 
#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 
#include 
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 
#include 
#include 
#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 
#include 
#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 (nreturn 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 
#include 
#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 
#include 
#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 
#include 
#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 
#include 
#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 
#include 
#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 (nreturn 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 
#include 
#include 
#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

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include <string.h>
#include 
#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<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+yrhs.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 
#include 
#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 
#include 
#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]return puts("0"),0;
    f[0] = 1;
    int l=0, r=0;
    s[n+1] = 2e18;
    REP(i,1,n+1) {
        while (r1&&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]1;
    }
    int ans = (f[n+1]-f[n])%P;
    if (ans<0) ans+=P;
    printf("%d\n",ans);
}
View Code

14. 3673 NRE

大意: 序列$a$初始全$0$, 给定01序列$b$, $q$个操作$(l,r)$, 表示把$a_l,...,a_r$全改为$1$. 求选择一些操作, 使得最后$a,b$的汉明距离最小

刚开始想着枚举位置$dp$, $wa$了一发才发现转移是不对的. 正解是把所有区间按左端点排序, 记$dp_i$为只考虑前$i$个区间的答案, 那么有

$$dp_i =   \begin{cases} dp_j+s_0[r_i]-s_0[r_j],  & l_j\le l_i\le r_j\le r_i \\ dp_j+s_0[r_i]-s_0[l_i-1]+s_1[l_i-1]-s_1[r_j], & r_j

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 2e5+10;
int n, b[N], s[2][N];
vector<int> g[N];
int c0[N<<2],c1[N<<2],pos[N<<2];
void build(int o, int l, int r) {
    c0[o] = c1[o] = INF;
    if (l==r) pos[l] = o;
    else build(ls),build(rs);
}
int qry(int *c, int o, int l, int r, int ql, int qr) {
    if (ql<=l&&r<=qr) return c[o];
    if (mid>=qr) return qry(c,ls,ql,qr);
    if (midreturn qry(c,rs,ql,qr);
    return min(qry(c,ls,ql,qr),qry(c,rs,ql,qr));
}
void upd(int *c, int o, int v) {
    o = pos[o];
    c[o] = min(c[o], v);
    while (o>>=1) c[o] = min(c[lc],c[rc]);
}
int main() {
    scanf("%d",&n);
    REP(i,1,n) { 
        scanf("%d",b+i);
        s[0][i]=s[0][i-1];
        s[1][i]=s[1][i-1];
        ++s[b[i]][i];
    }
    build(1,0,n),upd(c0,0,0),upd(c1,0,0);
    int q;
    scanf("%d",&q);
    while (q--) {
        int l, r;
        scanf("%d%d", &l, &r);
        g[l].pb(r);
    }
    int ans = s[1][n];
    REP(l,1,n) for (int r:g[l]) {
        int x = qry(c0,1,0,n,l,r)+s[0][r];
        x = min(x, qry(c1,1,0,n,0,l-1)+s[0][r]-s[0][l-1]+s[1][l-1]);
        upd(c0,r,x-s[0][r]);
        upd(c1,r,x-s[1][r]);
        ans = min(ans, x+s[1][n]-s[1][r]);
    }
    printf("%d\n", ans);
}
View Code

15. 3733 Papple Sort

大意: 给定字符串, 每次操作交换相邻字符, 求变成回文所需要最少操作数.

考虑每种字符的出现位置, 同种字符间显然不会产生交换, 那么每种字符相对位置是不变的, 假设出现位置为$p_1,p_2,...,p_r$, 那么最终$p_1$与$p_r$配对, $p_2$与$p_{r-1}$配对, 以此类推. 记$p_1,p_2,...,p_{r/2}$为$A$, 其余为$B$. 也就是说最优情况$A$全在左侧, $B$全在右侧. 可以发现对于每种局面, 固定$A$不动, 依次把$B$贪心放到$A$的对称位置一定最优. 所以贪心从左到右枚举, 每次遍历到$A$时, $A$的位置一定是在左半边(因为这个$A$是当前未匹配的位置最小的$A$), 把对应的$B$移到对应位置即可. 最后需要注意特判长度为奇数的情况, 需要把中间的字符留到最后再移动. 具体实现用树状数组模拟即可.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+10;
int n, vis[N], c[N];
char s[N];
vector<int> g[N];
void add(int x, int v) {
    for (; x<=n; x+=x&-x) c[x]+=v;
}
int qry(int x) {
    int r = 0;
    for (; x; x^=x&-x) r+=c[x];
    return r;
}
int main() {
    scanf("%s",s+1);
    n = strlen(s+1);
    int d = 0;
    REP(i,0,1) g[i].clear();
    REP(i,1,n) c[i]=0,vis[i]=0;
    REP(i,1,n) s[i]-='a',d^=1<<s[i],g[s[i]].pb(i);
    if (d&(d-1)) {cout<<-1<continue;}
    ll ans = 0;
    int now = n;
    REP(i,1,n) if (!vis[i]) {
        int nxt = g[s[i]].back();
        g[s[i]].pop_back();
        int x = nxt-qry(nxt);
        if (i==nxt) ans += max((n+1)/2-x,0);
        else ans += max(now---x,0), add(nxt+1,1);
        vis[nxt] = 1;
    }
    printf("%lld\n", ans);
}
View Code

16. 1982 Arrays and Palindrome

大意: 有两个序列$a,b$, 保证$sum(a)=N,sum(b)=N$. 满足对于任意序列: 若前$a_1$个字符,接着$a_2$个字符,...都是回文串. 前$b_1$个字符, 接着前$b_2$个字符,...都是回文串. 那么这个序列所有字符相等. 现在给出$a$的一个排列$A$, 求恢复序列$a,b$.

奇数个数$>2$不成立, 否则把奇数放两侧, 让$b[1]=a[1]+1$,$b[m]=a[m]-1$,其余$b[i]=a[i]$即可.要注意特判$m=1$的情况, 和两个奇数都为$1$的情况

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+50;
int n, m, a[N], b[N];

int main() {
    scanf("%d%d", &n, &m);
    vector<int> v[2];
    REP(i,1,m) { 
        int t;
        scanf("%d", &t);
        v[t&1].pb(t);
    }
    if (m==1) {
        if (n==1) return puts("1\n1\n1"),0;
        return printf("%d\n%d\n%d %d\n",n,2,1,n-1),0;
    }
    if (v[1].size()>2) return puts("Impossible"),0;
    if (v[1].size()==2) {
        a[1] = v[1][0], a[m] = v[1][1];
        if (a[1]>a[m]) swap(a[1],a[m]);
        b[1] = a[1]+1;
        REP(i,2,m-1) { 
            a[i] = v[0].back(), v[0].pop_back();
            b[i] = a[i];
        }
        b[m] = a[m]-1;
    }
    else if (v[1].size()==1) {
        a[1] = v[1][0], b[1] = a[1]+1;
        REP(i,2,m) {
            a[i] = v[0].back(), v[0].pop_back();
            b[i] = a[i];
        }
        b[m] = a[m]-1;
    }
    else {
        a[1] = v[0].back(), b[1] = a[1]+1;
        v[0].pop_back();
        REP(i,2,m) {
            a[i] = v[0].back(), v[0].pop_back();
            b[i] = a[i];
        }
        b[m] = a[m]-1;
    }
    REP(i,1,m) printf("%d ",a[i]);hr;
    if (!b[m]) --m;
    printf("%d\n",m);
    REP(i,1,m) printf("%d ",b[i]);hr;
}
View Code

17. 1983 BBQ Hard

大意: $n$个套餐, 第$i$个套餐有一根钎子, $a_i$个牛肉, $b_i$个青椒. 钎子有标号, 牛肉青椒无标号. 求选出两个套餐组成串成烤肉串的方案. 具体可以看样例图解.

容易得到答案为$\sum\limits_{1\le i转化为求$\sum\limits_{i=1}^n\sum\limits_{j=1}^n \frac{(a_i+a_j+b_i+b_j)!}{(a_i+a_j)!(b_i+b_j)!}$

可以发现答案为所有$(-a_i,-b_i)$走到$(a_j,b_j)$的格路径数, $S$连向每个点$(-a,-b)$, $(a,b)$连向$T$, $dp$求出$S$到$T$的路径条数即可 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 4050, M = 2010, S = 2e5+10;
int n, a[S], b[S], dp[N][N], fac[S], ifac[S];
void add(int &a, int b) {a+=b;if (a>=P) a-=P;}
int C(int n, int m) {
    return (ll)fac[n]*ifac[m]%P*ifac[n-m]%P;
}
int main() {
    fac[0]=1;
    REP(i,1,S-1) fac[i]=(ll)fac[i-1]*i%P;
    ifac[S-1]=inv(fac[S-1]);
    PER(i,0,S-2) ifac[i]=(ll)ifac[i+1]*(i+1)%P;
    scanf("%d", &n);
    REP(i,1,n) scanf("%d%d",a+i,b+i),++dp[-a[i]+M][-b[i]+M];
    REP(i,-M,M) REP(j,-M,M) if (dp[i+M][j+M]) {
        add(dp[i+M+1][j+M],dp[i+M][j+M]);
        add(dp[i+M][j+M+1],dp[i+M][j+M]);
    }
    int ans = 0;
    REP(i,1,n) ans = (ans+dp[a[i]+M][b[i]+M])%P;
    REP(i,1,n) ans = (ans-C(2*a[i]+2*b[i],2*a[i]))%P;
    if (ans<0) ans += P;
    ans = (ll)ans*inv(2)%P;
    printf("%d\n", ans);
}
View Code

18. 1984 Wide Swap

大意: 给定一个排列$p$, 每次操作选择$i,j$, 要求$j-i\ge k, abs(p_i-p_j)=1$, 然后交换$p_i,p_j$. 可以进行人一次操作, 求使排列$p$字典序最小方案.

agc的题都好有意思. 核心观察是可以用$pos_i$表示$i$在$p$中的位置, 那么就转化为每次交换相邻数, 要求最后$1$尽量在最前, 然后$2$尽量在最前, 以此类推. 这样就转化为[HNOI2015]菜肴制作, 建图拓扑即可. 不过这个题似乎直接用小根堆拓扑就能得到正确答案, 可能是因为这题建出的图的性质比较特殊.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+10;
int n, k, a[N], pos[N], deg[N], f[N];
vector<int> g[N];

int main() {
    scanf("%d%d", &n, &k);
    REP(i,1,n) scanf("%d", a+i);
    REP(i,1,n) pos[a[i]]=i;
    set<int> L, R;
    REP(i,1,k) R.insert(a[i]);
    REP(i,1,n) g[i].clear(),deg[i]=0;
    //L维护i的数在pos中的位置
    REP(i,1,n) {
        auto p = L.upper_bound(a[i]);
        if (p!=L.end()) {
            g[pos[*p]].pb(i);
            ++deg[i];
        }
        p = R.upper_bound(a[i]);
        if (p!=R.end()) {
            g[pos[*p]].pb(i);
            ++deg[i];
        }
        L.insert(a[i]), R.erase(a[i]);
        if (i-k+1>0) L.erase(a[i-k+1]);
        if (i+k<=n) R.insert(a[i+k]);
    }
    priority_queue<int> q;
    REP(i,1,n) if (!deg[i]) q.push(i);
    PER(i,1,n) {
        int u = q.top(); q.pop();
        f[i] = u;
        for (int v:g[u]) {
            if (!--deg[v]) q.push(v);
        }
    }
    REP(i,1,n) a[f[i]] = i;
    REP(i,1,n) printf("%d\n",a[i]);
}
View Code

19. 1999 Candy Piles

大意: $n$堆石子, 两人轮流操作, 每次有两种选择: (1)把石子最多的堆的石子全拿走 (2)每个非空堆拿一个. 拿走最后一个的人输, 求最后胜负情况.

石子降序排序, 每堆看成一个矩形, 然后转成棋盘博弈问题. 可以发现一个对角线上的点状态相同, 求出初始位置所在对角线与所有矩形边界的交点判断一下即可.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+10;
int n, a[N];

int main() {
    scanf("%d", &n);
    REP(i,1,n) scanf("%d", a+i);
    sort(a+1,a+1+n,greater<int>());
    REP(i,1,n) {
        int j = i;
        while (j1]==a[i]) ++j;
        if ((j-a[j])%2&&(i-1<=a[j]&&a[j]<=j||a[j+1]<=j&&j<=a[j])) return puts("First"),0;
        i = j;
    }
    puts("Second");
}
View Code 

20. 1998 Stamp Rally

大意: 给定无向连通图, 每次询问两人分别从点$x,y$出发, 一共遍历$z$个点后结束, 求遍历到的边的最大标号的最小值

单询问的话可以直接从小到大添边, 当$x,y$所在连通块大小不少于$z$的时候就为答案. 多组询问套个整体二分即可. 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 2e6+10;
int n,m,q,now,ans[N];
pii a[N];
struct {int x,y,z,id;} e[N],q1[N],q2[N];
int fa[N],sz[N];
int find(int x) {while (fa[x]!=x) x=fa[x]; return x;}
vectorint*,int> > L[N], R[N];
void init() {
    REP(i,1,m) {
        int u = find(a[i].x), v = find(a[i].y);
        if (u==v) continue;
        if (sz[u]<sz[v]) swap(u,v);
        L[i].emplace_back(sz+u,sz[u]);
        L[i].emplace_back(fa+v,fa[v]);
        sz[u] += sz[v], fa[v] = u;
        R[i].emplace_back(sz+u,sz[u]);
        R[i].emplace_back(fa+v,fa[v]);
    }
    now = m;
}
void add(int id) {
    for (auto &t:R[id]) *t.x=t.y;
}
void del(int id) {
    for (auto &t:L[id]) *t.x=t.y;
}
void solve(int s, int t, int l, int r) {
    if (l==r) {REP(i,s,t) ans[e[i].id]=l;return;}
    while (nownow);
    while (now>mid) del(now--);
    int l1 = 0, l2 = 0;
    REP(i,s,t) {
        int a = find(e[i].x), b = find(e[i].y), cnt = sz[a];
        if (a!=b) cnt += sz[b];
        if (cnt>=e[i].z) q1[++l1] = e[i];
        else q2[++l2] = e[i];
    }
    REP(i,1,l1) e[s+i-1]=q1[i];
    REP(i,1,l2) e[s+i-1+l1]=q2[i];
    if (l1) solve(s,s+l1-1,l,mid);
    if (l2) solve(s+l1,t,mid+1,r);
}
int main() {
    scanf("%d%d",&n,&m);
    REP(i,1,n) sz[i] = 1, fa[i] = i;
    REP(i,1,m) scanf("%d%d",&a[i].x,&a[i].y);
    init();
    scanf("%d", &q);
    REP(i,1,q) e[i].id=i,scanf("%d%d%d",&e[i].x,&e[i].y,&e[i].z);
    solve(1,q,1,m);
    REP(i,1,q) printf("%d\n",ans[i]);
}
View Code

21. 2040 Best Representation

大意: 若一个字符串$x$满足长度至少为$1$, 对于任意串$y$,任意整数$k\le 2$, $x$不等于$y$重复$k$次, 那么$x$为好字符串. 若一个字符串的划分的每一部分都是好字符串, 那么这个划分为好划分. 给定字符串$w$, 求$w$最小的好划分, 以及最小好划分的个数.

最小好划分大小要么为$n$, 要么为$1$, 要么为$2$. 这样枚举间断点, $kmp$求一下循环节即可.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+50;
int n;
char s[N], t[N];
int pre[N], suf[N];
void getfail(char *s, int *f, int n) {
    int j = 0;
    REP(i,1,n-1) {
        while (j&&s[i]!=s[j]) j=f[j];
        if (s[i]==s[j]) ++j;
        f[i+1] = j;
    }
}

int main() {
    scanf("%s", s+1);
    n = strlen(s+1);
    REP(i,1,n) t[i] = s[n-i+1];
    getfail(s+1,pre,n);
    getfail(t+1,suf,n);
    int len = n%(n-pre[n])==0?n/(n-pre[n]):1;
    if (len==n||len==1) return printf("%d\n1\n",len),0;
    int ans = 0;
    REP(i,1,n-1) {
        int x = i%(i-pre[i])==0?i/(i-pre[i]):1;
        int y = (n-i)%(n-i-suf[n-i])==0?(n-i)/(n-i-suf[n-i]):1;
        if (x==1&&y==1) ++ans;
    }
    printf("2\n%d\n", ans);
}
View Code 

22. 2042 Colorful Slimes

大意: $n$种史莱姆, 抓第$i$种花费$a_i$时间, 可以花费$x$时间把已经抓到的史莱姆种类循环右移一下, 求最少时间抓完$n$种史莱姆.

假设循环右移$t$次, 那么相当于第$i$种史莱姆可以取$[i-t+1,i]$的最小值, 所以枚举右移次数即可. 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 4e3+10;
int n, x, a[N], b[N];

int main() {
    scanf("%d%d", &n, &x);
    REP(i,1,n) scanf("%d", a+i),a[i+n]=a[i];
    memset(b,0x3f,sizeof b);
    ll ans = 1e18;
    REP(j,0,n) {
        REP(i,1,n) b[i]=min(b[i],a[i-j+n]);
        ans = min(ans, accumulate(b+1,b+1+n,0ll)+(ll)j*x);
    }
    printf("%lld\n", ans);
}
View Code 

23. 2045 Salvage Robots

大意: 给定棋盘, 有一个出口, 一些点有机器人, 每次操作选一个方向, 让所有机器人向该方向走一格, 走出边界的机器人立即死亡, 走到出口的得救. 可以操作任意次, 求最多救多少个机器人.

范围很小, 可以直接暴力区间$dp$. 卡空间, 需要滚动一下数组 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 110;
short h, w, a[N][N];
char s[N][N];
short dp[N][N][N];
short C(int x1, int y1, int x2, int y2) {
    if (x1>x2||y1>y2) return 0;
    --y1,--x1;
    return a[x2][y2]-a[x1][y2]-a[x2][y1]+a[x1][y1];
}
void chkmax(short &a, short b) {a0;}
int main() {
    scanf("%hd%hd", &h, &w);
    int x, y;
    REP(i,1,h) { 
        scanf("%s",s[i]+1);
        REP(j,1,w) a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+(s[i][j]=='o');
        REP(j,1,w) if (s[i][j]=='E') x = i, y = j;
    }
    short ans = 0;
    PER(a,1,x) REP(c,x,h) PER(b,1,y) REP(d,y,w) {
        int x1=1-x+c,x2=h-x+a;
        int y1=1-y+d,y2=w-y+b;
        short r = dp[b][c][d];
        if (a>1) {
            chkmax(dp[b][c][d],r+C(max(x1,a-1),max(y1,b),min(x2,a-1),min(y2,d)));
        }
        if (c<h) {
            chkmax(dp[b][c+1][d],r+C(max(x1,c+1),max(y1,b),min(x2,c+1),min(y2,d)));
        }
        if (b>1) {
            chkmax(dp[b-1][c][d],r+C(max(x1,a),max(y1,b-1),min(x2,c),min(y2,b-1)));
        }
        if (d<w) {
            chkmax(dp[b][c][d+1],r+C(max(x1,a),max(y1,d+1),min(x2,c),min(y2,d+1)));
        }
        chkmax(ans,r);
    }
    printf("%hd\n", ans);
}
View Code

24. 3734 Christmas Tree

大意: 要求用$A$条长度不超过$B$的链合并为一颗给定树, 求$(A,B)$字典序最小值

先考虑$A$的最小值, 假设奇数度数的点有$O$个, 那么显然$O$是偶数, 并且$A$的最小值就为$\frac{O}{2}$. 这是因为每次在一棵树上接上一条链, 最多产生两个奇数度数的点. 所以最小值就是$\frac{O}{2}$, 每次都选两个奇数度数点作为路径端点就可以达到$\frac{O}{2}$. 然后求$B$最小值, 问题可以转化为给定$O$个特殊点, 要求两两组合的路径长度最大值的最小值, 可以二分答案然后树形dp求出. $dp$时要选一个奇数度数的点作为根, 保证根节点儿子数为奇数.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+20;
int n;
vector<int> g[N], h[N];

int dfs(int x, int f, int d) {
    h[x].clear();
    for (int y:g[x]) if (y!=f) h[x].pb(dfs(y,x,d));    
    if (h[x].size()%2==0) h[x].pb(0);
    if (h[x].size()==1) return h[x][0]+1;
    sort(h[x].begin(),h[x].end());
    int sz=h[x].size(),l=0,r=sz-1,ans=-1;
    while (l<=r) {
        int ql=0,qr=sz-1,ok=1;
        while (1) {
            if (ql==mid) ++ql;
            if (qr==mid) --qr;
            if (ql>qr) break;
            if (h[x][ql]+h[x][qr]>d) ok=0;
            ++ql,--qr;
        }
        if (ok) ans=mid,r=mid-1;
        else l=mid+1;
    }
    return ans<0?INF:h[x][ans]+1; 
}

int main() {
    scanf("%d", &n);
    REP(i,2,n) {
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].pb(v),g[v].pb(u);
    }
    int cnt = 0, rt = 0;
    REP(i,1,n) if (g[i].size()&1) rt = i, ++cnt;
    cnt /= 2;
    int l = 0, r = n-1, ans;
    while (l<=r) {
        if (dfs(rt,0,mid)<=mid+1) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d %d\n", cnt, ans);
}
View Code

25. 2689 Prime Flip

大意: 给定$01$序列, 每次操作选一个长度为奇素数的区间翻转, 求最少多少次使序列全零.

奇素数区间花费为$1$, 偶数长的区间花费为$2$, 否则花费为$3$

可以先差分一下, 转化为每次选两个相差为奇素数的点改变颜色, 可以先跑出最大匹配, 尽量匹配奇素数区间, 然后匹配偶数区间, 最后若有剩余则花费为3

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

const int N = 1e6+10, S = N-2, T = N-1, INF = 0x3f3f3f3f;
const int M = 1e7+10;
int n, m, f[M];
struct edge {
    int to,w,next;
    edge(int to=0,int w=0,int next=0):to(to),w(w),next(next){}
} e[N];
int head[N], dep[N], vis[N], cur[N], cnt=1;
queue<int> Q;
int bfs() {
    REP(i,1,m) dep[i]=INF,vis[i]=0,cur[i]=head[i];
    dep[S]=INF,vis[S]=0,cur[S]=head[S];
    dep[T]=INF,vis[T]=0,cur[T]=head[T];
    dep[S]=0,Q.push(S);
    while (Q.size()) {
        int u = Q.front(); Q.pop();
        for (int i=head[u]; i; i=e[i].next) {
            if (dep[e[i].to]>dep[u]+1&&e[i].w) {
                dep[e[i].to]=dep[u]+1;
                Q.push(e[i].to);
            }
        }
    }
    return dep[T]!=INF;
}
int dfs(int x, int w) {
    if (x==T) return w;
    int used = 0;
    for (int i=cur[x]; i; i=e[i].next) {
        cur[x] = i;
        if (dep[e[i].to]==dep[x]+1&&e[i].w) {
            int f = dfs(e[i].to,min(w-used,e[i].w));
            if (f) used+=f,e[i].w-=f,e[i^1].w+=f;
            if (used==w) break;
        }
    }
    return used;
}
int dinic() {
    int ans = 0;
    while (bfs()) ans+=dfs(S,INF);
    return ans;
}
void add(int u, int v, int w) {
    e[++cnt] = edge(v,w,head[u]);
    head[u] = cnt;
    e[++cnt] = edge(u,0,head[v]);
    head[v] = cnt;
}

int chk(int x) {
    if (x<=2) return 0;
    int mx = sqrt(x+0.5);
    REP(i,2,mx) if (x%i==0) return 0;
    return 1;
}
int main() {
    scanf("%d", &n);
    REP(i,1,n) { 
        int t;
        scanf("%d", &t);
        f[t] = 1;
    }
    vector A, B;
    REP(i,1,M-1) if (f[i]!=f[i-1]) {
        if (i&1) A.pb(pii(i,++m)),add(S,m,1);
        else B.pb(pii(i,++m)),add(m,T,1);
    }
    for (auto t:A) for (auto tt:B) { 
        if (chk(abs(t.x-tt.x))) {
            add(t.y,tt.y,1);
        }
    }
    int flow = dinic();
    int ans = flow, x = A.size()-flow, y = B.size()-flow;
    ans += x/2*2, x %= 2;
    ans += y/2*2, y %= 2;
    if (x) ans += 3;
    printf("%d\n", ans);
}
View Code

26. 2698 Don't Be a Subsequence

大意: 给定一个串$s$, 求最短的串$t$, 使得$t$不是$s$的子串, 多种方案时输出字典序最小的.

对于一个串的答案可以贪心, 每当出现'a'-'z'的所有字母后划分一个区间, 最短长度就为区间数+1. 所以可以预处理出每个后缀的答案, 然后序列自动机贪心即可

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+50;
char s[N], ans[N];
int n, f[N], nxt[N][26];

int main() {
    scanf("%s",s+1);
    n = strlen(s+1);
    REP(i,0,25) nxt[n+1][i]=nxt[n+2][i]=n+1;
    int sta = 0, mx = (1<<26)-1;
    f[n+1] = 1;
    PER(i,1,n) {
        sta |= 1<'a';
        f[i] = f[i+1];
        if (sta==mx) ++f[i], sta = 0;
        memcpy(nxt[i],nxt[i+1],sizeof nxt[0]);
        nxt[i][s[i]-'a']=i;
    }
    f[n+1] = 0;
    int len = f[1], now = 1;
    REP(i,1,len) {
        REP(j,'a','z') {
            int t = nxt[now][j-'a']+1;
            if (i+f[t]<=len) {
                now = t, ans[i] = j;
                break;
            }
        }
    }
    puts(ans+1);
}
View Code 

27. 2699 Flip and Rectangles

大意: 给定棋盘, 每次操作翻转一行或一列, 求最大全$1$矩形面积

可以发现一个矩形如果能全变为$1$, 那么等价于任意一个$2\times 2$的矩形$1$的个数为偶数. 所以就转化为简单的最大全$1$矩形问题, 要特判一下长或宽为$1$的矩形

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 2e3+10;
int n, m, a[N][N], L[N][N], R[N][N], H[N][N];
char s[N][N];

int main() {
    scanf("%d%d", &n, &m);
    REP(i,1,n) scanf("%s", s[i]+1);
    REP(i,1,n-1) REP(j,1,m-1) {
        int t = (s[i][j]=='#')^(s[i+1][j]=='#')^(s[i][j+1]=='#')^(s[i+1][j+1]=='#');
        a[i][j] = !t;
    }
    REP(i,1,n-1) {
        REP(j,1,m-1) if (a[i][j]) {
            if (a[i][j-1]) L[i][j] = L[i][j-1];
            else L[i][j] = j;
        }
        PER(j,1,m-1) if (a[i][j]) {
            if (a[i][j+1]) R[i][j] = R[i][j+1];
            else R[i][j] = j;
        }
    }
    int ans = max(n,m);
    REP(i,1,n-1) REP(j,1,m-1) if (a[i][j]) {
        H[i][j] = H[i-1][j]+1;
        if (H[i-1][j]) {
            L[i][j] = max(L[i][j],L[i-1][j]);
            R[i][j] = min(R[i][j],R[i-1][j]);
        }
        ans = max(ans, (H[i][j]+1)*(R[i][j]-L[i][j]+2));
    }
    printf("%d\n", ans);
}
View Code

28. 2363 Tautonym Puzzle

大意: 若一个序列满足前一半与后一半相同, 则为好序列. 求构造一个序列使得非空好子序列个数为$N$

构造题. 一个想法是连续$x$个数贡献是$2^x-1$, 然后直接划分成若干个连续相同的段, 但是这样长度会超限.

一个倍增的做法是, 对于每种数字只用两次, 第一次全在串$A$内, 第二次全在串$B$内, 并且考虑空子序列的情况.

假设$AB$的答案为$x$, 那么新添一个数字$t$, $tAtB$答案为$2x$, $tABt$答案为$x+1$, 直接递归即可.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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



#ifdef ONLINE_JUDGE
const int N = 1e6+50;
#else
const int N = 1e2+10;
#endif


deque<int> A, B;
int now;

void solve(ll n) {
    if (n==1) return;
    solve(n>>1);
    ++now;
    A.push_front(now);
    B.push_front(now);
    if (n&1) {
        ++now;
        A.push_front(now);
        B.push_back(now);
    }
}

int main() {
    ll n;
    cin>>n;
    solve(n+1);
    cout<endl;
    for (auto t:A) cout<' ';
    for (auto t:B) cout<' ';
    cout<<endl;
}
View Code

29. 2366 Prefix Median 

大意: 给定长$2n-1$的序列$a$, 求所有$a$的排列中, 能生成多少种序列$b$. 其中$b_i$为$a$中前$2i-1$个数的中位数.

好难的$dp$题, 参考了好多篇题解才大概懂, 不愧是AGC的F题

先把$a$排序, 可以注意到$b$必须满足三个条件

  • $b_{i}$是$\{ a_i,a_{i+1},...,a_{2n-i}\}$中的一个数
  • 不存在$i
  • 不存在$ib_{i}>b_{j+1}$

下面证明这三个条件是充分的, 也就是说证明所有满足三个条件的序列$b$都可以构造出对应的$a$.

只考虑$a$是排列的情况, 有重复元素的话证明类似

显然$b_n=n, b_{n-1}$为$n-1,n,n+1$其中之一

  • 若$b_{n-1}=n-1$, 那么从$\{n,...,2n-1\}$中取出最小的两个未在$b_1,...,b_{n-1}$中出现过的数, 填到$a_{2n-2},a_{2n-1}$中
  • 若$b_{n-1}=n+1$, 那么从$\{1,...,n\}$中取出最大的两个未在$b_1,...,b_{n-1}$中出现过的数, 填到$a_{2n-2},a_{2n-1}$中
  • 若$b_{n-1}=n$, 那么从$\{1,...,n-1\}$中取最大未出现的数, $\{n+1,...,2n-1\}$中取最小, 填到$a_{2n-2},a_{2n-1}$中

这样取完后, $b_{n-1}$一定为剩余数的中位数, 由于三个条件的限制, 那么$b_{n-2}$一定是剩余数中中间的三个之一, 所以递归下去即可构造出$a$.

然后就是简单$dp$了, 设$dp_{i,j,k}$为已经填完$b_{i+1},...,b_{n}$, 现在要填$b_{i}$, 比$b_{i+1}$小的可选数有$j$个, 比$b_{i+1}$大的可选数有$k$个的方案数. 枚举$b_{i}$的选取情况转移即可, 前缀和优化一下可以达到复杂度$O(n^3)$, 直接暴力转移$O(n^4)$也能过.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 200;
int n, a[N], dp[2][N][N];

void add(int &a, int b) {a+=b;if (a>=P) a-=P;}

int main() {
    scanf("%d", &n);
    int m = 2*n-1;
    REP(i,1,m) scanf("%d", a+i);
    sort(a+1,a+1+m);
    int cur = 0;
    dp[0][0][0] = 1;
    PER(i,1,n-1) {
        cur ^= 1;
        memset(dp[cur],0,sizeof dp[0]);
        int l = a[i]!=a[i+1], r = a[2*n-i-1]!=a[2*n-i];
        REP(j,0,m) REP(k,0,m) {
            int &ret = dp[!cur][j][k];
            if (!ret) continue;
            //b[i]=b[i+1]
            add(dp[cur][j+l][k+r],ret);
            //b[i]
            REP(t,0,j+l-1) add(dp[cur][t][k+r+1],ret);
            //b[i]>b[i+1]
            REP(t,0,k+r-1) add(dp[cur][j+l+1][t],ret);
        }
    }
    int ans = 0;
    REP(i,0,m) REP(j,0,m) add(ans,dp[cur][i][j]);
    printf("%d\n", ans);
}
O(n^4)
#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 310;
int n, a[N], dp[2][N][N];
int sum1[N][N], sum2[N][N];
void add(int &a, int b) {a+=b;if (a>=P) a-=P;}

int main() {
    scanf("%d", &n);
    int m = 2*n-1;
    REP(i,1,m) scanf("%d", a+i);
    sort(a+1,a+1+m);
    dp[0][0][0] = 1;
    int cur = 0, ans = 0;
    PER(i,0,n-1) {
        REP(j,0,m) PER(k,1,m) { 
            add(sum1[k-1][j],sum1[k][j]);
            add(sum2[j][k-1],sum2[j][k]);
        }
        REP(j,0,m) REP(k,0,m) { 
            add(dp[cur][j][k],sum1[j][k]),sum1[j][k]=0;
            add(dp[cur][j][k],sum2[j][k]),sum2[j][k]=0;
            dp[!cur][j][k] = 0;
            if (!i) add(ans,dp[cur][j][k]);
        }
        if (!i) break;
        cur ^= 1;
        int l = a[i]!=a[i+1], r = a[2*n-i-1]!=a[2*n-i];
        REP(j,0,m) REP(k,0,m) {
            int &ret = dp[!cur][j][k];
            if (!ret) continue;
            //b[i]=b[i+1]
            add(dp[cur][j+l][k+r],ret);
            //b[i]
            if (j+l-1>=0) add(sum1[j+l-1][k+r+1],ret);
            //b[i]>b[i+1]
            if (k+r-1>=0) add(sum2[j+l+1][k+r-1],ret);
        }
    }
    printf("%d\n", ans);
}
O(n^3)

30. 2703 Shift and Flip

大意: 给定两个01串$A,B$, 每次操作把$A$循环左移或右移, 或者选一个$B_i=1$的$i$, 翻转$A_i$, 求最少操作次数使得$A,B$相同

假设有一个计数器初始为$0$, 左移$-1$,右移$+1$, 最小值为$L$,最大值为$R$,最终值为$d$.

可以发现如果$d$固定, 那么$A$中每个位置的数是否需要翻转就已经固定, 并且可以知道最优情况要么是先左移到$L$,右移到$R$,再移到$d$. 要么是先右移到$R$,左移到$L$,再移到$d$.

然后考虑翻转操作, $B$中位置$i$的$1$可以翻转的区间就为$[i+L,i+R]$, 只要所有的$1$对应区间覆盖到所有$A$中需要翻转的位置, 那么就合法, 翻转的花费就为$A$中需要翻转的位数.

所以可以$O(n^2)$枚举$L,d$, 求出最小合法的右移距离$R$即可.

具体实现的话, 注意到$L$递增的时候, $R$是单调递减的, 可以直接用可撤销链表模拟.

 感觉这个题还是比较考验码力的, 花了两个小时才写完, 主要还是我可撤销链表和双指针不熟练, 以后要找时间再打一遍这个题

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e4+50;
int n, vis[N];
char a[N],b[N],c[N];
struct _ {
    int p,l,r;
} L[N],R[N];
vector > tag[N];
vector<int> tag2[N];

int solve(int d) {
    REP(i,0,n) tag[i].clear(),tag2[i].clear();
    int ans = 0, cnt = 0;
    REP(i,1,n) { 
        vis[i] = 0;
        int nxt = i-d;
        if (nxt<=0) nxt += n;
        c[nxt] = b[i]^a[i];
        if (b[i]) L[++cnt].p = i;
    }
    REP(i,1,n) ans += c[i];
    REP(i,1,cnt) { 
        L[i].l=i-1,L[i].r=i+1,R[i]=L[i];
    }
    L[0].r = 1, R[0] = L[0];
    if (!ans) return d;
    int tot = 0, pl = 0, pr = 0;
    REP(i,1,n) if (b[i]) { 
        vis[i] = 1;
        if (c[i]) ++tot;
    }
    if (tot==ans) return ans+d;
    auto add = [&](_ f[], int &p, int tp) {
        p += tp;
        for (int i=f[0].r; i!=cnt+1; i=f[i].r) {
            int nxt = f[i].p-tp;
            if (nxt==0) nxt=n;
            if (nxt>n) nxt=1;
            f[i].p = nxt;
            if (tp<0) {
                if (vis[nxt]) {
                    tag[abs(p)].pb(pair<_*,_>(&f[f[i].l],f[f[i].l]));
                    tag[abs(p)].pb(pair<_*,_>(&f[f[i].r],f[f[i].r]));
                    f[f[i].l].r=f[i].r;
                    f[f[i].r].l=f[i].l;
                }
                else { 
                    tag2[abs(p)].pb(nxt);
                    vis[nxt] = 1;
                    if (c[nxt]) ++tot;
                }
            }
            else {
                if (vis[nxt]>=10) f[f[i].l].r=f[i].r,f[f[i].r].l=f[i].l;
                else {
                    if (!vis[nxt]&&c[nxt]) ++tot;
                    vis[nxt] += 10;
                }
            }
        }
    };
    while (tot1);
    int ret = min(abs(pl)+abs(pr-pl)+abs(d-pr),abs(pr)+abs(pl-pr)+abs(d-pl));
    do {
        for (auto &t:tag[abs(pl)]) *(t.x) = t.y;
        for (auto &t:tag2[abs(pl)]) {
            --vis[t];
            if (!vis[t]&&c[t]) --tot;
        }
        ++pl;
        while (tot1);
        ret = min(ret, min(abs(pl)+abs(pr-pl)+abs(d-pr),abs(pr)+abs(pl-pr)+abs(d-pl)));
    } while (pl);
    return ans+ret;
}


int solve() {
    int ans = 1e9;
    REP(d,0,n-1) {
        ans = min(ans, solve(d));
        char t = a[n];
        PER(i,2,n) a[i]=a[i-1];
        a[1] = t;
    }
    return ans;
}

char tmp[N];

int main() {
    scanf("%s%s",a+1,b+1);
    n = strlen(a+1);
    if (strcmp(a+1,b+1)==0) return puts("0"),0;
    if (*max_element(b+1,b+1+n)=='0') return puts("-1"),0;
    REP(i,1,n) a[i]-='0',b[i]-='0',tmp[i]=a[i];
    int ans = solve();
    REP(i,1,n) a[i]=tmp[i];
    reverse(a+1,a+1+n);
    reverse(b+1,b+1+n);
    ans = min(ans, solve());
    printf("%d\n", ans);
}
View Code

31. 2396 Infinite Sequence

大意: 定义一个由$\{1,...,n\}$组成的无限长序列, 满足(1)第$n$项及以后的项全相等(2)第$i$项之后的$a_i$项相等, 求所有不同的序列个数

设${dp}_i$为$i...n$的填数方案, 那么有${dp}_n=n,{dp}_{n-1}=n^2$

对于$i\le n-2$, 若填$1$, 方案数为${dp}_{i+1}$

若连续填两个$>1$的数, 那么之后全部相同, 方案数为$(n-1)^2$

若填一个$>1$的数$x$, 后面接$x$个$1$, 那么总方案数为$\sum\limits_{j=i+3}^n f[j]$, 再加上填的$1$超出$n$的部分$i+1$

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+10;
int n, f[N];

int main() {
    scanf("%d", &n);
    f[n] = n, f[n-1] = (ll)n*n%P;
    int s = 0;
    PER(i,1,n-2) {
        s = (s+f[i+3])%P;
        f[i] = (s+i+1+(ll)(n-1)*(n-1)+f[i+1])%P;
    }
    printf("%d\n", f[1]);
}
View Code

32. 2401 Alice in linear land

大意: 有一条直线, $Alice$初始在$0$, 要去位置$D$. 给定一个序列$d$, 第$i$步若向终点走$d_i$能缩短到终点的距离就走$d_i$, 否则停在原地不动. 给定$Q$个询问$q_i$, 若修改$d_{q_i}$的值后, 能使$Alice$不能到达位置$D$输出YES, 否则输出NO

假设$a_i$表示前$i$次操作后的距终点距离, 那么修改第$q$次操作后, 距离范围就为$[0,a_{q-1}]$

设$b_i$为最小数, 满足前$i$步后若在$b_i$则不能达到终点, 那么显然只要$b_q\le a_{q-1}$答案就为YES, 可以得到

  • 显然$b_n=1$
  • 若$b_{i+1}\le \lfloor\frac{d_{i+1}}{2}\rfloor, b_{i}=b_{i+1}$
  • 否则, $b_{i}=b_{i+1}+d_{i+1}$
#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 1e6+50;
ll d[N],a[N];
ll b[N];

int main() {
    int n=rd(),D=rd();
    REP(i,1,n) d[i]=rd();
    a[0] = D;
    REP(i,1,n) a[i] = min(a[i-1],abs(a[i-1]-d[i]));
    b[n] = 1;
    PER(i,1,n-1) {
        if (b[i+1]<=d[i+1]/2) b[i]=b[i+1];
        else b[i]=b[i+1]+d[i+1];
    }
    int q=rd();
    REP(i,1,q) {
        int x = rd();
        puts(b[x]<=a[x-1]?"YES":"NO");
    }
}
View Code

33. 2062 ~K Perm Counting

大意: 给定$n,k$, 求不存在$|p_i-i|=k$的$n$排列个数.

假设有一个二分图, $L$部为序列下标, $R$部为值, 那么排列$p$对应了一个完美匹配.

考虑一个只含边$(L_i,R_{i+k}),(L_i,R_{i-k})$的二分图

假设它大小为$k$的匹配数为$M_k$, 根据容斥得到答案就为$\sum\limits_{i=0}^n (-1)^i M_i (n-i)!$

$M_k$可以很容易用$O(n^2)$的$dp$求出.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
const int P = 924844033, 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 = 2e3+10;
int n,k,vis[2*N],dp[2*N][N][2],fac[N];
void add(int &a, int b) {a+=b;if(a>=P)a-=P;}

int main() {
    fac[0]=1;
    REP(i,1,N-1) fac[i]=(ll)fac[i-1]*i%P;
    scanf("%d%d",&n,&k);
    int cnt = 0;
    //分别对2k条链进行dp,每条边挂在下一个点上,链头没有边
    REP(i,1,k) {
        for(int j=i;j<=n;j+=k) vis[++cnt]=i!=j;
        for(int j=i;j<=n;j+=k) vis[++cnt]=i!=j;
    }
    dp[0][0][0] = 1;
    REP(i,1,2*n) REP(j,0,min(n,i)) {
        add(dp[i][j][0],dp[i-1][j][0]);
        add(dp[i][j][0],dp[i-1][j][1]);
        if (vis[i]) add(dp[i][j+1][1],dp[i-1][j][0]);
    }
    int ans = 0;
    REP(i,0,n) {
        int t = (dp[2*n][i][0]+dp[2*n][i][1])%P;
        if (i&1) ans=(ans-(ll)t*fac[n-i])%P;
        else ans=(ans+(ll)t*fac[n-i])%P;
    }
    if (ans<0) ans += P;
    printf("%d\n", ans);
}
O(n^2)

实际上可以发现对于一条长$x$的链, 匹配数为$k$就相当于取$k$条不相邻的边

那么方案数就为$\binom{x-k+1}{k}$, 用$NTT$把所有链贡献乘一下即可, 复杂度$O(nlogn)$

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<using namespace std;
typedef long long ll;
const int N = 1e4+10, P = 924844033, G = 5, Gi = 554906420;
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;}

int lim,l,A[N],B[N],R[N];
void init(int n) {
    for (lim=1,l=0; lim<=n; lim<<=1,++l) ;
    REP(i,0,lim-1) R[i]=(R[i>>1]>>1)|((i&1)<<(l-1));
}

void NTT(int *J, int tp=1) {
    REP(i,0,lim-1) if (i<R[i]) swap(J[i],J[R[i]]);
    for (int j=1; j1) {
        ll T = qpow(tp==1?G:Gi,(P-1)/(j<<1));
        for (int k=0; k1) {
            ll t = 1;
            for (int l=0; lP) {
                int y = t*J[k+j+l]%P;
                J[k+j+l] = (J[k+l]-y)%P;
                J[k+l] = (J[k+l]+y)%P;
            }
        }
    }
    if (tp==-1) {
        ll inv = qpow(lim, P-2);
        REP(i,0,lim-1) J[i]=(ll)inv*J[i]%P;
    }
}

int n,k,fac[N],ifac[N],a[N],b[N];
int C(int n, int m) {
    if (m>n) return 0;
    return (ll)fac[n]*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, &k);
    int x=n/k,y=n/k+1;
    REP(i,0,x) a[i]=C(x-i,i);
    REP(i,0,y) b[i]=C(y-i,i);
    init(2*n+2);
    NTT(a),NTT(b);
    REP(i,0,lim-1) a[i]=qpow(a[i],(k-n%k)*2)*qpow(b[i],n%k*2)%P;
    NTT(a,-1);
    int ans = 0;
    REP(i,0,n) {
        if (i&1) ans=(ans-(ll)a[i]*fac[n-i])%P;
        else ans=(ans+(ll)a[i]*fac[n-i])%P;
    }
    if (ans<0) ans += P;
    printf("%d\n", ans);
}
O(nlogn)

34. 4511 Tree Burning

大意: 长$L$的环, 初始在$0$, 有$n$棵树, 每次顺时针或逆时针走, 烧掉第一棵为被烧的树, 求烧完所有树的最大距离和.

这个agc的$B$题竟然不会, 看了题解才懂

可以发现最优解一定形如LLLRLRLRL, 或者RRRLRLRLR

枚举转折点, 可以发现终点贡献是终点到起点的距离, 其余点的贡献是它到起点距离的2倍. 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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



#ifdef ONLINE_JUDGE
const int N = 1e6+50;
#else
const int N = 1e2+10;
#endif



int L, n, a[N], d[N];
ll ans;
void solve() {
    ll tot = 0;
    PER(i,1,n) {
        //倒序枚举, 逐步添点
        d[i] = a[i], tot += d[i];
        int len = n-i+1, p;
        //p为转折点
        if (len&1) p = n-len/2;
        else {
            p = n-len/2+1;
            tot -= d[p];
            d[p] = L-d[p];
            tot += d[p];
        }
        ans = max(ans, tot*2-d[p]);
    }
}

int main() {
    scanf("%d%d",&L,&n);
    REP(i,1,n) scanf("%d",a+i);
    solve();
    exit(0);
    REP(i,1,n) a[i] = L-a[i];
    reverse(a+1,a+1+n);
    solve();
    printf("%lld\n", ans);
}
View Code

35. 4512 Coloring Torus

大意: 给定$k$种颜色, 求构造一个$n\times n$的棋盘, 要求每个格子都涂上一种颜色, 每种颜色都至少用一次. 对于同种颜色的格子, 要求相邻格每种颜色的出现次数相同. 相邻指的是循环相邻.

注意到$a_{i,j}=(i+j)\mod n$时一定合法. 若$n$为偶数, 把奇数列全加上$n$仍然合法, 把$n+i$换成$i$仍然合法. 所以就可以构造出$[n,2n]$的所有$k$. 

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 2e3+10;
int n,k,a[N][N];

int main() {
    scanf("%d",&k);
    if (k==1) return puts("1\n1"),0;
    n = (k+3)/4*2;
    REP(i,1,n) REP(j,1,n) a[i][j]=(i+j)%n+i%2*n;
    REP(i,1,n) REP(j,1,n) if (a[i][j]>=k) a[i][j]-=n;
    printf("%d\n", n);
    REP(i,1,n) {
        REP(j,1,n) printf("%d ", a[i][j]+1);hr;
    }
}
View Code

36. 4513 Inversion Sum

大意: 给定$n$元素序列$a$, $q$个操作$(x,y)$表示交换$a_x,a_y$. 按顺序执行$q$次操作, 可以选择跳过操作. 对于$2^q$种情况, 求出逆序对的和.

$O(n^3)$的$dp$很容易, 只需要设$dp_{i,j,k}$为前$i$次操作$a_j>a_k$的方案数, 暴力转移即可.

实际上每次转移影响到的状态会很少, 只有$O(n)$, 可以把方案数全除以$2$, 转成概率$dp$来做.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 210;
int n,q,a[N],x[N],y[N];
int dp[N][N][N];

int main() {
    scanf("%d%d",&n,&q);
    REP(i,1,n) scanf("%d",a+i);
    REP(i,1,q) { 
        scanf("%d%d",x+i,y+i);
    }
    REP(i,1,n) REP(j,1,n) dp[0][i][j] = a[i]>a[j];
    REP(z,1,q) { 
        REP(i,1,n) REP(j,1,n) {
            if (x[z]==i) {
                if (y[z]==j) {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][j][i];
                }
                else {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][y[z]][j];
                }
            }
            else if (x[z]==j) {
                if (y[z]==i) {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][j][i];
                }
                else {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][i][z[y]];
                }
            }
            else {
                if (y[z]==i) {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][x[z]][j];
                }
                else if (y[z]==j) {
                    dp[z][i][j] = dp[z-1][i][j]+dp[z-1][i][x[z]];
                }
                else {
                    dp[z][i][j] = dp[z-1][i][j]*2;
                }
            }
        }
    }
    int ans = 0;
    REP(i,1,n) REP(j,i+1,n) ans = (ans+dp[q][i][j])%P;
    printf("%d\n", ans);
}
O(n^3)
#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 ll inv2 = (P+1)/2;
const int N = 3300;
int n,q,a[N],x[N],y[N];
int dp[N][N];
struct {int x,y;ll v;} s[N*10];

int main() {
    scanf("%d%d",&n,&q);
    REP(i,1,n) scanf("%d",a+i);
    REP(i,1,q) scanf("%d%d",x+i,y+i);
    REP(i,1,n) REP(j,1,n) dp[i][j] = a[i]>a[j];
    REP(z,1,q) { 
        int top = 0;
        REP(i,1,n) {
            if (i!=y[z]) { 
                s[++top] = {x[z],i,(dp[x[z]][i]+dp[y[z]][i])*inv2%P};
                s[++top] = {i,x[z],(dp[i][x[z]]+dp[i][y[z]])*inv2%P};
            }
            else { 
                s[++top] = {x[z],y[z],(dp[x[z]][y[z]]+dp[y[z]][x[z]])*inv2%P};
            }
            if (i!=x[z]) {
                s[++top] = {y[z],i,(dp[y[z]][i]+dp[x[z]][i])*inv2%P};
                s[++top] = {i,y[z],(dp[i][y[z]]+dp[i][x[z]])*inv2%P};
            }
            else {
                s[++top] = {y[z],x[z],(dp[x[z]][y[z]]+dp[y[z]][x[z]])*inv2%P};
            }
        }
        REP(i,1,top) dp[s[i].x][s[i].y]=s[i].v;
    }
    int ans = 0;
    REP(i,1,n) REP(j,i+1,n) ans = (ans+dp[i][j])%P;
    ans = ans*qpow(2,q)%P;
    printf("%d\n", ans);
}
O(n^2)

37. 3971 rng_10s

大意: 初始商店$A$瓶饮料, 一个人每天早上来买$B$瓶, 晚上若商店饮料数不超过$C$那么老板补充$D$瓶饮料, 求这个人是否每天都能买到$B$瓶.

$B

38. 2003 BBuBBBlesort!

大意: 给定序列$a$, 元素各不相同, 每次操作翻转连续两位或三位, 求翻转两位的最少次数, 使序列升序排列.

题目保证没有重复元素, 那就等价于给定一个排列. 翻转三位相当于是交换间隔2的元素, 那么每次交换不改变与最终位置的奇偶性

39. 4169 Colorful Sequences

大意: 若一个序列存在一段$k$的子串为$1...k$的排列, 那么这个序列为合法序列. 给定$n,m,k$,给定长$m$的序列$A$, 求$A$在所有长$n$的合法序列中的出现次数和.

$A$在所有序列中的出现次数为$(n-m+1)k^{n-m}$, 转化为求$A$在不合法序列中的出现次数和.

(1)若$A$已经合法, 那么答案为$0$.

(2)若$A$中元素互不相同, 那么可以$dp$求出长$m$的所有互不相同的序列在所有不合法序列中的方案, 最后再除以$\frac{k!}{(k-m)!}$.

设状态$(i,j)$表示长$i$,最后一段连续互不相同的段长$j$, 那么转移图很容易画出来.

定义所有$m\le j\le k-1$的点的权值为$1$, 否则为$0$, 那么答案就为从$(0,0)$到$(n,1...k-1)$所有路径的点权和.

记$f_{i,j}$为$(0,0)$到$(i,j)$的路径数, $g_{i,j}$为$(0,0)$到$(i,j)$的所有路径的点权和, 转移可以后缀优化在$O(nk)$的时间求出.

(3)若$A$中元素有相同的, 那么存在一个互不相同的前缀和后缀, 两部分是独立的, 并且总的方案只与这两部分有关. 可以枚举$A$在所有不合法序列中的出现位置, $dp$算出对应不合法序列数, $dp$过程和(2)类似.

#include 
#include 
#include 
#include 
#include 
#include <set>
#include 
#include 
#include <string>
#include 
#include 
#include 
#include 
#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<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 = 2.6e4+10, M = 410;
int n, k, m, a[N];
int f[N][M],g[N][M];
int vis[N],fac[N],ifac[N];
void add(int &a, ll b) {a=(a+b)%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%d", &n, &k, &m);
    REP(i,1,m) scanf("%d",a+i);
    int ma = 0, now = 0;
    REP(i,1,m) {
        while (now1]]) vis[a[++now]]=1;
        ma = max(ma, now-i+1);
        --vis[a[i]];
    }
    int tot = (n-m+1ll)*qpow(k,n-m)%P;
    if (ma==k) return printf("%d\n",tot),0;
    if (ma==m) {
        f[0][0] = 1;
        REP(i,1,n) {
            int s1 = 0, s2 = 0;
            PER(j,1,min(k-1,i)) {
                add(s1,f[i-1][j]),add(s2,g[i-1][j]);
                add(f[i][j]=s1,(ll)(k-j+1)*f[i-1][j-1]);
                add(g[i][j]=s2,(ll)(k-j+1)*g[i-1][j-1]);
                if (j>=m) add(g[i][j],f[i][j]);
            }
        }
        int ans = 0;
        REP(i,1,k-1) (ans += g[n][i])%=P;
        ans = (ll)ans*ifac[k]%P*fac[k-m]%P;
        ans = (tot-ans+P)%P;
        printf("%d\n", ans);
    }
    else {
        int L = 0, R = 0;
        memset(vis,0,sizeof vis);
        while (!vis[a[L+1]]) vis[a[++L]]=1;
        memset(vis,0,sizeof vis);
        while (!vis[a[m-R]]) vis[a[m-R++]]=1;
        f[0][L] = g[0][R] = 1;
        REP(i,1,n) {
            int s1 = 0, s2 = 0;
            PER(j,1,k-1) {
                add(s1,f[i-1][j]),add(s2,g[i-1][j]);
                add(f[i][j]=s1,(ll)(k-j+1)*f[i-1][j-1]);
                add(g[i][j]=s2,(ll)(k-j+1)*g[i-1][j-1]);
            }
        }
        int ans = 0;
        REP(i,0,n-m) {
            int s1 = 0, s2 = 0;
            REP(j,1,k-1) add(s1,f[i][j]),add(s2,g[n-m-i][j]);
            add(ans,(ll)s1*s2);
        }
        ans = (tot-ans+P)%P;
        printf("%d\n", ans);
    }
}
View Code

 

你可能感兴趣的:(AtCoder练习)