海亮02/18杂题

海亮02/18杂题

个人题单

T1

link

题意

给你一个长度为 \(n\) 的数列,然后给你 \(q\) 个交换或不交换操作,你可以选择操作或者不操作,问所有情况下逆序对的总和。

答案需要对 \(10 ^ 9 + 7\) 取模。

\(n\leq 3000\)\(q\leq 3000\)

题解

发现一个问题,对于操作执不执行很难描述,怎么办?

我们考虑设 \(f(i,j)\) 表示 \(a_i>a_j\) 的情况占总情况数的比例(其实就是看、作期望啦),然后在最后把总情况数 \(2^q\) 乘一下即可。

然后考虑每个操作对 \(f\) 的影响。

不难发现,有影响的只有 \(f(x,i)\)(当然还有 \(f(i,x)\))和 \(f(y,i)\)(当然还有 \(f(i,y)\))。

发现影响是 \(O(n)\) 的,直接更新即可。

然后就做完了,整道题最难的点就是第一步转化拆贡献。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
bool stmemory;
namespace Call_me_Eric{
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e3 + 10, mod = 1e9 + 7;
int n, a, b;
int f[maxn][maxn][2], sum[maxn][maxn][2];
int qpow(int x,int a){
    int res = 1;
    while(a){
        if(a & 1){res = res * x % mod;}
        x = x * x % mod;a >>= 1;
    }
    return res;
}
int pw[maxn], pre[maxn];
void main(){
    n = read(); a = read(); b = read();if(a < b)swap(a, b);
    pw[0] = 1;for(int i = 1;i < maxn;i++){pw[i] = pw[i - 1] * 2 % mod;}
    pre[0] = f[0][0][0] = f[0][0][1] = sum[0][0][0] = sum[0][0][1] = 1;
    int ans = 0;
    for(int i = 1;i <= n;i++){
        sum[i][0][1] = f[i][0][1] = (pre[i - 1] - (i - b >= 0 ? pre[i - b] : 0) + mod) % mod;
        for(int j = 1;j <= i;j++){
            if(j >= b){
                int val = sum[i - b][j - b][0];
                if(j >= a)ans = (ans + val * pw[max(n - i - 1,0ll)] % mod) % mod;
                else f[i][j][1] = val;
            }
        }
        for(int j = 1;j <= i;j++){
            int val = sum[i - 1][j - 1][1];
            if(j >= a)ans = (ans + val * pw[max(n - i - 1,0ll)] % mod) % mod;
            else f[i][j][0] = val;
        }
        pre[i] = pre[i - 1];
        for(int j = 1;j <= i;j++){
            pre[i] = (pre[i] + f[i][j][0]) % mod;
            sum[i][j][0] = (sum[i - 1][j - 1][0] + f[i][j][0]) % mod;
            sum[i][j][1] = (sum[i - 1][j - 1][1] + f[i][j][1]) % mod;
        }
    }
    printf("%lld\n",ans);
    return;
}
};
bool edmemory;
signed main(){
    auto stclock = clock();
    Call_me_Eric::main();
    auto edclock = clock();
    cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n";
    cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n";
    return 0;
}

T2

link

题意

Snuke 君有长为 \(N\) 的字符串 \(x\),最初 \(x\) 的所有字符都是 \(0\)。Snuke 君可以按照任意顺序进行任意次数以下两种操作:

  • 选择 \(x\) 中连续的长为 \(A\) 的子串,将它们全部设为 \(0\)
  • 选择 \(x\) 中连续的长为 \(B\) 的子串,将它们全部设为 \(1\)

请计算操作结束后的可能达成的不同的 \(x\) 的数量,对 \(10^9+7\) 取模。

题解

考虑对于一个最终串,有没有办法变成全 \(0\)

首先能够想到两个结论:

  • 可以将长度 \(\ge A\) 的串全变成 \(0\)
  • 可以将长度 \(\ge B\) 的串全变成 \(1\)

那么如果能够将整个串都变成 \(1\),那么就一定能够变成全 \(0\)

然后你发现这个时候 \(0,1\) 等价了,你的目标是将一个串变成全相同即可。

然后不妨设 \(A\ge B\)(否则同时交换 \(A,B\)\(0,1\))。

然后先给出一个序列能够变成全相同的充要条件:存在一个长度 \(\ge A\) 的子串,其中不包含 \(<B\) 的连续 \(1\) 串。

尝试证明:
必要性:最后一次填写 \(1\) 串的时候,串长度一定 \(\ge B\)
充分性:先尝试将这个子串外面的随便搞,然后最后一步填到这个子串,然后再填一步 \(1\)

于是你就可以快乐DP了。

具体的,设 \(f_{i,j,0/1}\) 表示到 \(i\) 为止,已经有长度为 \(j\) 的、不包含 \(<B\)\(1\) 的段,目前最后一位颜色是 \(0/1\)

然后前缀和优化一下就可以 \(O(n^2)\) 了。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
bool stmemory;
namespace Call_me_Eric{
inline int read(){
    int x = 0, f = 1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 5e3 + 10, mod = 1e9 + 7;
int n, a, b;
int f[maxn][maxn][2], sum[maxn][maxn][2];
int qpow(int x,int a){
    int res = 1;
    while(a){
        if(a & 1){res = res * x % mod;}
        x = x * x % mod;a >>= 1;
    }
    return res;
}
int pw[maxn], pre[maxn];
void main(){
    n = read(); a = read(); b = read();if(a < b)swap(a, b);
    pw[0] = 1;for(int i = 1;i < maxn;i++){pw[i] = pw[i - 1] * 2 % mod;}
    pre[0] = f[0][0][0] = f[0][0][1] = sum[0][0][0] = sum[0][0][1] = 1;
    int ans = 0;
    for(int i = 1;i <= n;i++){
        sum[i][0][1] = f[i][0][1] = (pre[i - 1] - (i - b >= 0 ? pre[i - b] : 0) + mod) % mod;
        for(int j = 1;j <= i;j++){
            if(j >= b){
                int val = sum[i - b][j - b][0];
                if(j >= a)ans = (ans + val * pw[max(n - i - 1,0ll)] % mod) % mod;
                else f[i][j][1] = val;
            }
        }
        for(int j = 1;j <= i;j++){
            int val = sum[i - 1][j - 1][1];
            if(j >= a)ans = (ans + val * pw[max(n - i - 1,0ll)] % mod) % mod;
            else f[i][j][0] = val;
        }
        pre[i] = pre[i - 1];
        for(int j = 1;j <= i;j++){
            pre[i] = (pre[i] + f[i][j][0]) % mod;
            sum[i][j][0] = (sum[i - 1][j - 1][0] + f[i][j][0]) % mod;
            sum[i][j][1] = (sum[i - 1][j - 1][1] + f[i][j][1]) % mod;
        }
    }
    printf("%lld\n",ans);
    return;
}
};
bool edmemory;
signed main(){
    auto stclock = clock();
    Call_me_Eric::main();
    auto edclock = clock();
    cerr << (&stmemory - &edmemory) / 1024.0 / 1024.0 << " Mib cost.\n";
    cerr << (edclock - stclock) * 1.0 / CLOCKS_PER_SEC << " Sec cost.\n";
    return 0;
}

T3

link

题意

给出一个 \((n+2)\times(n+2)\) 的网格图。
定义一个排列 \(P\) 和它的一个函数 \(f(P)\)
其中 \(f(P)\) 表示,将所有 \((i,P_i)\) 点标记,则 \(f(P)\) 等于所有从 \((0,0)\to (n+1,n+1)\) 且不经过任意一个标记点的方案数。
现在这个排列有些位置没有填好(对应的 \(P_i=-1\)),问所有可能填好的排列情况的 \(f(P')\) 的总和是多少。

题解

首先先想一个问题,如果给出一个填好的排列,怎么算 \(f(P)\)
这里给出一个trick,利用容斥来计数。
具体而言,让一条路径的贡献是 \((-1)^{经过标记点的数量}\),证明显然。
然后对于填好的序列,设 \(f_{i,j}\) 表示到点 \((i,j)\) 的方案数,则 \((-1)^{lim_{i,j}}\times f_{i,j}\to f_{i+1,j}\)\(f_{i,j+1}\)

那对于没填好的序列呢,怎么办?
统计下未确定的点的总数为 \(m\)
\(f_{i,j,k,a,b}\) 表示到点 \((i,j)\),已经钦定了 \(k\) 个未确定的标记点的位置,且当前状态下行 \(i\) (\(a=0\Leftrightarrow\) 没有标记点 / \(a=1\Leftrightarrow\) 有标记点),列 \(j\) (\(b=0\Leftrightarrow\) 没有标记点 / \(b=1\Leftrightarrow\) 有标记点)的方案数。
显然有

\[+f_{i,j,k,a,b}\to f_{i+1,j,k,row_{i+1},b} \]

\[+f_{i,j,k,a,b}\to f_{i,j+1,k,a,line_{j+1}} \]

\(a=0,b=0\)

\[-f_{i,j,k,a,b}\to f_{i+1,j,k+1,row_{i+1},1} \]

\[-f_{i,j,k,a,b}\to f_{i,j+1,k+1,1,line_{j+1}} \]

最后答案

\[ans=\sum_{i=0}^mf_{n+1,n+1,i,0,0}\times (m-i)! \]

没有然后了。
浇浇计数题QWQ

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){
    int x=  0, f=  1;char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1;ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = (x << 1) + (x << 3) + (ch ^ 48);ch = getchar();}
    return x * f;
}
const int maxn = 210, mod = 998244353;
int f[maxn][maxn][maxn][2][2];
int n, a[maxn], m,fac[maxn];
bool lim[maxn][maxn],line[maxn],row[maxn];
signed main(){
    fac[0] = 1; n = read();
    for(int i = 1;i <= n;i++)a[i] = read();
    for(int i = 1;i <= n;i++){
        fac[i] = fac[i - 1] * i % mod;
        if(a[i] != -1){
            m++; lim[i][a[i]] = line[a[i]] = row[i] = 1;
        }
    }
    m = n - m;int *x;
    f[0][0][0][1][1] = 1;
    for(int i = 0;i <= n + 1;i++)for(int j = 0;j <= n + 1;j++)
        for(int k = 0;k <= m;k++)
            for(int a = 0;a < 2;a++)for(int b = 0;b < 2;b++){
                int t = f[i][j][k][a][b];if(!t || lim[i][j])continue;
                if(!lim[i + 1][j]){
                    x = &f[i + 1][j][k][row[i + 1]][b];
                    *x += t;if(*x > mod)*x -= mod;
                }
                if(!lim[i][j + 1]){
                    x = &f[i][j + 1][k][a][line[j + 1]];
                    *x += t;if(*x > mod)*x -= mod;
                }
                if(a || b)continue;

                if(!lim[i + 1][j]){
                    x = &f[i + 1][j][k + 1][row[i + 1]][1];
                    *x -= t;if(*x < 0)*x += mod;
                }
                if(!lim[i][j + 1]){
                    x = &f[i][j + 1][k + 1][1][line[j + 1]];
                    *x -= t;if(*x < 0)*x += mod;
                }
            }
    int ans = 0;
    for(int i = 0;i <= m;i++){ans = (ans + f[n + 1][n + 1][i][0][0] * fac[m - i]) % mod;}
    printf("%lld\n",ans);
    return 0;
}
posted @ 2024-02-18 15:05  Call_me_Eric  阅读(44)  评论(0)    收藏  举报
Live2D