【多校训练】2021牛客多校4

【前言】
这场打的比较舒服,俺代码打的还是很快的,不过数学题啥的还是做的太水了。
rk21,校2/9

A. Course

【题意】

n n n个课程, 每门课有学分 (范围 [ 1 , 5 ] [1,5] [1,5]), 且每门课都可以选无数次, 现在求选了恰好$ w $学分的方案数

题目还会给出一个培养方案 : 一个 n n n个点的有根树, 每个限制是 x x x的子树里的课的学分总和至少为 c [ x ] c[x] c[x]

【思路】

每个单点的生成函数为 g ( i ) = 1 + x s + x 2 s + ⋯ = 1 1 − x s g(i)=1+x^s+x^{2s}+\dots =\frac1{1-x^s} g(i)=1+xs+x2s+=1xs1

然后考虑限制的影响, 一个点的生成函数应该是 f ( x ) ⋅ ∏ y ∈ s o n ( x ) g ( y ) f(x)\cdot \prod_{y\in son(x)} g(y) f(x)yson(x)g(y)同时要把次数<c[x]的全部舍弃.

限定一个 l i m lim lim值(比如=25000), 让 f ( x ) f(x) f(x)的次数不超过 l i m lim lim

那么进行多项式乘法只需要NTT即可,每一个 ≤ l i m \leq lim lim的询问只需要输出 f ( 1 ) f(1) f(1) x w x^w xw的系数即可.

而对于一个大于 l i m lim lim的询问, 需要进行以下观察:

d p [ W ] dp[W] dp[W]为总分为W的选课方案, 则可以进行完全背包.

$dp[W,i]=\sum_{t=0}^{\lfloor W/s_y\rfloor}dp[W-t\cdot s_y,y] $

若取 若干个X, 与W模 l c m ( s 1 , s 2 , … , s n ) lcm(s_1,s_2,\dots,s_n) lcm(s1,s2,,sn)同余, 那么求和的过程应该是一样的.

那么可以看做可以用同一个多项式表示的函数的在不同点的值.

而树上背包进行了至多2n次合并, 所以令其为2n次的多项式, 取2n个点值即可算出W处的点值,

而lcm只有60, 所以可以取$ j,j+60, \dots, j+(2n)\cdot 60$, 其中j与W同余

用拉格朗日插值法即可算出.

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int mod=998244353;
const int N=80000;
ll ksm(ll a,int b,ll c=1){
    for(;b;b/=2,a=a*a%mod)
        if(b&1)c=c*a%mod;
    return c;
}
inline void reduce(int&x){x+=x>>31&mod;}
inline void reduce(ll&x){x+=x>>31&mod;}
int n_,lgn_,omg_[N],inv_[N];
void FFT_init(int n){
    for(lgn_=0;(1<<lgn_<=n);++lgn_); ;n_=1<<lgn_;
    int w=ksm(3,(mod-1)>>lgn_);omg_[0]=1;inv_[0]=0;
    for(int i=1;i<n_;++i){
        omg_[i]=(ll)omg_[i-1]*w%mod;
        inv_[i]=(inv_[i>>1]>>1)|((i&1)<<(lgn_-1));
    }
}
ll ww[N];
void FFT(int*a,int flag=0){
    for(int i=0;i<n_;++i)if(i<inv_[i])swap(a[i],a[inv_[i]]);
    for(int i=0;i<lgn_;++i){
        int s,t=1<<i;
        for(int j=0;j<t;++j)ww[j]=omg_[j<<(lgn_-i-1)];
        for(int j=0;j<n_;j+=t<<1){
            int*f=a+j,*g=a+j+t;
            for(int k=0;k<t;++k){
                s=g[k]*ww[k]%mod;
                reduce(g[k]=f[k]-s);
                reduce(f[k]+=s-mod);
            }
        }
    }
    if(flag){
        reverse(a+1,a+n_);
        int iv=(1-mod)/n_+mod;
        for(int i=0;i<n_;++i)a[i]=(ll)a[i]*iv%mod;
    }
}
void mul(int *a,int n,int *b,int m){
    // FFT_init(n+m+1);
    for(int i=n+1;i<n_;++i)a[i]=0;
    for(int i=m+1;i<n_;++i)b[i]=0;
    FFT(a);FFT(b);
    for(int i=0;i<n_;++i)a[i]=(ll)a[i]*b[i]%mod;
    FFT(a,1);
}
int lim;
int dp[110][N];
vector<int> adj[120];
int n,Q,s[120],c[120];
void dfs(int x,int fa){
    for(int i=0;i<lim;i+=s[x]) dp[x][i]=1;
    for(int &y:adj[x])if(y!=fa){
        dfs(y,x);
        mul(dp[x],lim,dp[y],lim);
    }
    for(int i=0;i<c[x];i++) dp[x][i]=0;
}
int X[N],Y[N];
int pre[N],suf[N];

int calc(int n,int w){
    ll ans=0;
    rep(i,1,n){
        ll tmp=1;
        rep(j,1,n)if(i!=j){
            reduce(tmp=tmp*(w-X[j])%mod);
            reduce(tmp=ksm((X[i]-X[j]),mod-2,tmp));
        }
        ans=(ans+tmp*Y[i]%mod)%mod;
    } 
    return ans;
}
int main(){
    //freopen("my.in","r",stdin);
    lim=25000;
    FFT_init(lim+lim+1);
    n=yh();Q=yh();
    rep(i,2,n) {
        int x=yh(),y=yh();
        adj[x].pb(y);adj[y].pb(x);
    }
    rep(i,1,n) s[i]=yh();
    rep(i,1,n) c[i]=yh();
    dfs(1,0);
    // rep(i,1,n){
    //     rep(j,0,13) cout<<dp[i][j]<<" ";
    //     puts("");
    // }
    while(Q--){
        int w=yh();
        if(w<lim)cout<<dp[1][w]<<hvie;
        else{
            int j;
            for(j=lim/2;j<=lim/2+60;j++)if(j%60==w%60) break;
            rep(i,1,2*n+1){
                X[i]=(i-1)*60+j;
                Y[i]=dp[1][X[i]];
            }
            cout<<calc(2*n+1,w)<<hvie;
        }
    }
    return 0;
}
B. Sample Game

【题意】

给定 1 ∼ n 1\sim n 1n每个数生成的概率,如果当前生成的数是已经生成的数中最大的,那么就接种生成,否则结束生成。得分为生成的数的数量的平方。

求期望得分。

【思路】

队友做的,懒得想了。

大概就是先求出长度的期望,这个转移方程显然,然后也可以通过这个求出长度平方的期望。

反正复杂度是 O ( n ) O(n) O(n)

【参考代码】

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=3e5+5,mod=998244353;
ll ksm(ll x, int P){
    int ans=1;
    for(;P;P>>=1,x=x*x%mod)if(P&1)ans=ans*x%mod;
    return ans;
}
int f[maxn],g[maxn];
int p[maxn],q[maxn];
signed main(){
    int n=yh();
    int tot=0;
    rep(i,1,n){
        p[i]=yh();
        tot=(tot+p[i])%mod;
    }
    rep(i,1,n) p[i]=p[i]*ksm(tot,mod-2)%mod,q[i]=(1+mod-p[i])%mod;
    int sum=0;
    dwn(i,n,1){
        f[i]=(1ll+sum)*ksm(q[i],mod-2)%mod;
        sum=(sum+f[i]*p[i]%mod)%mod;
    }

    int s2=0;
    int sf=0;
    dwn(i,n,1){
        sf=(sf+2*f[i]*p[i]%mod)%mod;
        g[i]=(1+sf+s2)%mod*ksm(q[i],mod-2)%mod;
        s2=(s2+g[i]*p[i]%mod)%mod;
        // cout<<i<<" "<<g[i]<<hvie;
    }
    cout<<g[1]<<hvie;
    return 0;
}
//27/2

C. LCS

【题意】

给定三个长度为 n n n字符串两两的LCS,构造一组合法解。

n ≤ 1000 n\leq 1000 n1000

【思路】

考虑这三个串互相的 LCS 为 x , y , z x,y,z x,y,z, 且$ x\geq y\geq z$

显然如果 x + y − n > z x+y-n>z x+yn>z, 则无解, 所以一定有 x + y − n ≤ z x+y-n\leq z x+ynz

我们先给这三个串加上一个$ z $个 $a $的前缀, 然后就变成了一个 x − z , y − z , 0 , n − z x-z,y-z,0,n-z xz,yz,0,nz 的同类问题

因为$ x+y-n<=z$, 所以 ( x − z ) + ( y − z ) < = n − z (x-z)+(y-z)<=n-z (xz)+(yz)<=nz, 所以我们给前两个串一起放上$ x-z$ 个 b b b, 后两个串一起放上 y − z y-z yz 个$ c$即可

【参考代码】

/*
 * @date:2021-07-26 12:38:45
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {
    return y > x ? x = y, 1 : 0;
}
template<class T, class G> bool chkMin(T &x, G y) {
    return y < x ? x = y, 1 : 0;
}

int a[3], b[3], n;
string s[3], ss[3];
int f[1005][1005];

int lcs(string &a, string &b) {
    memset(f, 0, sizeof f);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            if (i == 0 || j == 0) {
                if (a[i] == b[j]) f[i][j] = 1;
                else f[i][j] = 0;
            } else {
                if (a[i] != b[j]) f[i][j] = max(f[i - 1][j], f[i][j - 1]);
                else f[i][j] = f[i - 1][j - 1] + 1;
            }

        }
    }
    return f[n - 1][n - 1];
}

int main() {
    for (int i = 0; i < 3; ++i) cin >> a[i];
    for (int i = 0; i < 3; ++i) b[i] = a[i];
    cin >> n;
    sort(a, a + 3);
    if (a[2] + a[1] - a[0] > n) puts("NO");
    else {
        for (int i = 0; i < n; ++i) s[0] += 'a';
        for (int i = 0; i < a[0]; ++i) s[1] += 'a';
        for (int i = 0; i < n - a[0]; ++i) s[1] += 'b';
        for (int i = 0; i < a[2]; ++i) s[2] += 'a';
        for (int i = 0; i < a[1] - a[0]; ++i) s[2] += 'b';
        for (int i = 0; i < n - a[2] - a[1] + a[0]; ++i) s[2] += 'c';
        sort(s, s + 3);
        do {
            if (lcs(s[0], s[1]) != b[0]) continue;
            if (lcs(s[1], s[2]) != b[1]) continue;
            if (lcs(s[0], s[2]) != b[2]) continue;
            for (int i = 0; i < 3; ++i) cout << s[i] << endl;
            break;
        } while (next_permutation(s, s + 3));
    }
    return 0;
}
D. Rebuild Tree

【题意】

给定一颗 n n n个点带标号的生成树,对于 k ∈ [ 1 , K ] k\in [1,K] k[1,K]问有多少种方案,从生成树上删去 k k k条边后再加上 k k k条边还是一颗生成树。

n ≤ 5 × 1 0 4 , K ≤ 100 n\leq 5\times 10^4,K\leq 100 n5×104,K100

【思路】

加入删去了 k k k条边,剩下连通块的大小为 s [ 1 ] . . . s [ k + 1 ] s[1]...s[k+1] s[1]...s[k+1]

考虑prufer序列,实际上答案就是 n k − 2 ⋅ ∏ i = 1 k + 1 s [ i ] n^{k-2}\cdot \prod_{i=1}^{k+1} s[i] nk2i=1k+1s[i]

那么我们就要求每种删边方案的连通块大小乘积的和。

这个实际上不太好做,但可以转化为一个等价问题:删掉 k k k条边,且在每个连通块内都选了一个点的方案数。

f [ x ] [ k ] [ 0 / 1 ] f[x][k][0/1] f[x][k][0/1]表示对于子树 x x x,一共删了 k k k条边,目前 x x x所在连通块是否已经选了点的方案数,这个就可以做到 O ( n K ) O(nK) O(nK)

【参考代码】

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;

inline int add(int x, int y) {
    return x + y >= mod ? x + y - mod : x + y;
}
inline int mul(int x, int y) {
    return 1LL * x * y % mod;
}
inline int power(int x, int y) {
    int res = 1;
    for (; y; y >>= 1, x = mul(x, x)) if (y & 1) res = mul(res, x);
    return res;
}
const int MAXN = 5e4 + 5;
const int MAXK = 100 + 5;
vector<int> G[MAXN];
int f[MAXN][MAXK][2];
int n, k;

int dfs(int x, int fa) {
    f[x][1][0] = f[x][1][1] = 1;
    int s = 1;
    static int pf[MAXK][2];
    for (int v : G[x]) {
        if (v == fa) continue;
        int sv = dfs(v, x);
        int ns = min(s + sv, k + 1);
        for (int i = 1; i <= s; i++) {
            pf[i][0] = f[x][i][0], pf[i][1] = f[x][i][1];
            f[x][i][0] = f[x][i][1] = 0;
        }
        for (int i = 1; i <= s; i++)
            for (int j = 1, lim = min(sv, ns - i + 1); j <= lim; j++) {
                f[x][i + j][0] = add(f[x][i + j][0], mul(pf[i][0], f[v][j][1]));
                f[x][i + j][1] = add(f[x][i + j][1], mul(pf[i][1], f[v][j][1]));
                f[x][i + j - 1][0] = add(f[x][i + j - 1][0], mul(pf[i][0], f[v][j][0]));
                f[x][i + j - 1][1] = add(f[x][i + j - 1][1], add(mul(pf[i][0], f[v][j][1]), mul(pf[i][1], f[v][j][0])));
            }
        s = ns;
    }
    return s;
}

signed main() {
    cin >> n >> k;
    int u, v;
    for (int i = 1; i < n; i++) {
        cin >> u >> v;
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs(1, 0);
    cout << f[1][k + 1][1] * power(n, k - 1) % mod << endl;
    return 0;
}

E. Tree Xor

【题意】

给定一颗 n n n个点的树,树上每个点的点权在 [ l i , r i ] [l_i,r_i] [li,ri]范围内,再给出每条边两点的异或值,求有多少种可能的方案。

n ≤ 1 0 5 , W < 2 30 n\leq 10^5,W<2^{30} n105,W<230

【思路】

首先显然确定了一个点的权值,其他所有点的权值也唯一确定。以1为根,每个点到根的路径异或和再异或上根的权值就是这个点最终的权值。

那么问题就转化为了,根节点有多少种取值使得满足所有的 [ l i , r i ] [l_i,r_i] [li,ri]

实际上这个问题的子问题就是有多少个 x x x满足 x ⊗ a i ∈ [ l i , r i ] x\otimes a_i\in[l_i,r_i] xai[li,ri],或者,我们只需要考虑 x ⊗ a i ≥ l i x\otimes a_i\geq l_i xaili

这是一个典中典的问题——所有可行的 x x x在Trie树上对应的就是最多 log ⁡ W \log W logW个区间,于是我们只需要标记所有的区间,最后统计一遍标记了 2 n 2n 2n次(大于等于和小于等于都标记一次)的所有区间长度和即可。

复杂度 O ( n log ⁡ W ) O(n\log W) O(nlogW)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define rg register
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e5+10,M=30;

int ans,n;
bool vis[N];
pii inv[N];
vector<pii>G[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

int sz=1,tag[N*M*4];
struct NODE
{
    int l,r,len,ch[2];
}node[N*M*4];

void newnode(int &x,int l,int r,int c)
{
    x=++sz;
    int mid=(l+r)>>1;
    if(!c) node[x].l=l,node[x].r=mid;
    else node[x].l=mid+1,node[x].r=r;
    node[x].len=node[x].r-node[x].l+1;
}

void findupper(int x,int k,int lim,int d)
{
    if(d==-1) 
    {
        tag[x]++;
        return;
    }
    int tlim=(lim>>d)&1,tk=(k>>d)&1;
    if(tlim==0)
    {
        int ta=tk^1;
        if(!node[x].ch[ta]) 
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        ++tag[node[x].ch[ta]];

        ta=tk;
        if(!node[x].ch[ta]) 
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        findupper(node[x].ch[ta],k,lim,d-1);
    }
    else
    {
        int ta=tk^1;
        if(!node[x].ch[ta])
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        findupper(node[x].ch[ta],k,lim,d-1);
    }
}

void findlower(int x,int k,int lim,int d)
{
    if(d==-1) 
    {
        //printf("%d [%d , %d] is OK\n",d,node[x].l,node[x].r);
        tag[x]++;
        return;
    }
    int tlim=(lim>>d)&1,tk=(k>>d)&1;

    if(tlim==1)
    {
        int ta=tk;
        if(!node[x].ch[ta]) 
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        //printf("%d [%d , %d] is OK\n",d,node[node[x].ch[ta]].l,node[node[x].ch[ta]].r);
        ++tag[node[x].ch[ta]];

        ta=tk^1;
        if(!node[x].ch[ta]) 
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        findlower(node[x].ch[ta],k,lim,d-1);
    }
    else
    {
        int ta=tk;
        if(!node[x].ch[ta])
            newnode(node[x].ch[ta],node[x].l,node[x].r,ta);
        findlower(node[x].ch[ta],k,lim,d-1);
    }
}


void insert(int x,int now,int l,int r)
{
    //printf("nowinsert:%d %d %d\n",now,l,r);
    findupper(1,now,l,M-1);findlower(1,now,r,M-1);
}

void dfs(int x,int now)
{
    vis[x]=1;
    insert(x,now,inv[x].fi,inv[x].se);
    for(auto v:G[x]) 
        if(!vis[v.fi]) dfs(v.fi,now^v.se);
}

void dfsans(int x,int now)
{
    if(tag[x]+now==2*n)
    {
        ans+=node[x].len;
        //printf("find:%d %d\n",node[x].l,node[x].r);
        return;
    }
    if(node[x].ch[0]) dfsans(node[x].ch[0],now+tag[x]);
    if(node[x].ch[1]) dfsans(node[x].ch[1],now+tag[x]);
}

int main()
{
    n=read();
    for(int i=1;i<=n;++i) inv[i].fi=read(),inv[i].se=read();
    for(int i=1;i<n;++i)
    {
        int u=read(),v=read(),w=read();
        G[u].pb(mkp(v,w));G[v].pb(mkp(u,w));
    }
    node[1].l=0;node[1].r=(1<<(M))-1;node[1].len=(1<<(M));
    dfs(1,0);
    dfsans(1,0);
    printf("%d\n",ans);
    
    return 0;
}
F. Just a joke

【题意】

给定一幅无向图 G G G,两个人博弈,每次可以选择无向图上一条边删除,或选择一个没有环的连通集删除,不能删的输。

n ≤ 100 n\leq 100 n100

【思路】

第一种操作使得边数 − 1 -1 1,第二种操作使得点数 − k -k k,边数 − k + 1 -k+1 k+1,不论如何操作,边数+点数的奇偶性总会改变,因此判断 n + m n+m n+m的奇偶性即可。

【参考代码】

/*
 * @date:2021-07-26 12:28:49
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}

int N, M;

int main() {
    scanf("%d%d", &N, &M);
    if ((N + M) % 2 == 0) puts("Bob");
    else puts("Alice");
    return 0;
}
G.Product

【题意】

给出 n , k , D n,k,D n,k,D,对于每个非负整数序列, a i a_i ai,它的权值为
D ! ∏ i = 1 n ( a i + k ) ! \frac {D!} {\prod_{i=1}^n(a_i+k)!} i=1n(ai+k)!D!
求所有 ∑ i = 1 n a i = D \sum_{i=1}^n a_i=D i=1nai=D的权值和。

n , k ≤ 50 , D ≤ 1 0 8 n,k\leq 50,D\leq 10^8 n,k50,D108

【思路】

首先有结论:
∑ a i ≥ 0 , ∑ a i = D ∏ i 1 a i ! = n D D ! \sum_{a_i\geq 0,\sum a_i=D}\prod_i \frac 1 {a_i!}=\frac {n^D}{D!} ai0,ai=Diai!1=D!nD
于是题目等价于求:
D ! ∑ a i ≥ k , ∑ a i = D + n k ∏ i 1 a i ! D!\sum_{a_i\geq k,\sum a_i=D+nk}\prod_i\frac {1}{a_i!} D!aik,ai=D+nkiai!1
如果没有 ≥ k \geq k k的限制的话,答案就是:
D ! ∑ a i ≥ 0 , ∑ a i = D + n k ∏ i 1 a i ! = n D + n k ( D + n k ) ! D ! D!\sum_{a_i\geq 0,\sum a_i=D+nk}\prod_i\frac {1}{a_i!}=\frac {n^{D+nk}}{(D+nk)!}D! D!ai0,ai=D+nkiai!1=(D+nk)!nD+nkD!
现在有这个 k k k的限制,我们可以考虑用容斥,枚举 a i < k a_i<k ai<k的值,然后就变成了上面的问题,只是 n n n D D D需要改一下,DP的时候记上就行。

复杂度 O ( ( n k ) 2 ) O((nk)^2) O((nk)2)

【参考代码】

#include<bits/stdc++.h>
#define int long long
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=3e5+5,mod=998244353;
ll ksm(ll x, int P){
    int ans=1;
    for(;P;P>>=1,x=x*x%mod)if(P&1)ans=ans*x%mod;
    return ans;
}
// void inc(int &x,ll y){x=(x+y)%mod;}
void inc(ll &x,ll y){x=(x+y)%mod;}
int mul(int x,int y){return 1ll*x*y%mod;}
struct poly{
    int x[3000];
    int n;
    poly(){}
    poly(int n){rep(i,0,n) x[i]=0;x[n+1]=0;}
    void init(int n){
        rep(i,0,n) x[i]=0;
        this->n=n;
        x[n+1]=0;
    }
    void print(){
        cout<<n<<" : ";
        rep(i,0,n){
            cout<<x[i]<<" ";
        }
        puts("");
    }
}tmp,P[60];
poly operator*(const poly&a,const poly&b){
    int n=a.n,m=b.n;
    tmp.init(n+m);
    rep(i,0,n)rep(j,0,m){
        inc(tmp.x[i+j],mul(a.x[i],b.x[j]));
    }
    // cout<<n+m<<hvie;
    return tmp;
}

int fac[maxn],ifac[maxn];
int n,k,D;
int C[105][105];
int down[3000];
void init(){
    fac[0]=1;
    down[0]=1;
    rep(i,1,2999) down[i]=mul(down[i-1],D+n*k-i+1);
    rep(i,0,100){
        C[i][0]=1;
        if(i>0)fac[i]=mul(fac[i-1],i);
        rep(j,1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
    P[0].init(0); P[0].x[0]=1;
    P[1].init(k-1); rep(i,0,k-1) P[1].x[i]=ksm(fac[i],mod-2);
    rep(i,2,55){
        P[i]=P[i-1]*P[1];
        // cout<<i<<hvie;
    }
}
signed main(){
    // freopen("my.in","r",stdin);
    n=yh(),k=yh(),D=yh();
    if(k==0){
        cout<<ksm(n,D)<<hvie;
        return 0;
    }
    init();
    ll ans=0;
    rep(i,0,n){
        ll tmp=(i&1)?(mod-C[n][i]):C[n][i];
        ll tmp2=0;
        rep(s,0,(k-1)*i){
            inc(tmp2,mul(mul(P[i].x[s],ksm(n-i,D+1ll*n*k-s)),down[s]));
        }
        inc(ans,mul(tmp,tmp2));
    }
    ans=mul(ans,ksm(down[n*k],mod-2));
    cout<<ans<<hvie;
    return 0;
}


H. Convolution

【题意】

定义运算 ⊗ \otimes ,设 x = ∏ i p i a i , y = ∏ i p i b i x=\prod_ip_i^{a_i},y=\prod_ip_i^{b_i} x=ipiai,y=ipibi,则 x ⊕ y = ∏ i p i ∣ a i − b i ∣ x\oplus y=\prod_ip_i^{|a_i-b_i|} xy=ipiaibi,其中 p i p_i pi是所有的素数。

给定一个长度为 n n n的序列 a i a_i ai b i = ∑ 1 ≤ j , k ≤ n , j ⊗ k = i a j ⋅ k c b_i=\sum_{1\leq j,k\leq n,j\otimes k=i}a_j\cdot k^c bi=1j,kn,jk=iajkc

b i b_i bi对998244353取模后的异或和。

n ≤ 1 0 6 , c ≤ 1 0 9 n\leq 10^6,c\leq 10^9 n106,c109

【思路】

首先, x ⊗ y = x y gcd ⁡ ( x , y ) 2 x\otimes y=\frac {xy}{\gcd(x,y)^2} xy=gcd(x,y)2xy,那么我们可以枚举 x ′ = x gcd ⁡ ( x , y ) , y ′ = y gcd ⁡ ( x , y ) x'=\frac x{\gcd(x,y)},y'=\frac y{\gcd(x,y)} x=gcd(x,y)x,y=gcd(x,y)y,再枚举 d = gcd ⁡ ( x , y ) d=\gcd(x,y) d=gcd(x,y),则
b k = ∑ x ′ y ′ = k ∑ d = 1 min ⁡ ( n x ′ , n y ′ ) a x ′ d ( y ′ d ) c b_k=\sum_{x'y'=k}\sum_{d=1}^{\min(\frac n {x'},\frac n {y'})}a_{x'd}(y'd)^c bk=xy=kd=1min(xn,yn)axd(yd)c
显然整个枚举的复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)的,而对于后面的计算我们有:
a x ′ d ( y ′ d ) c = a x ′ d ( x ′ d ) c ( y ′ x ′ ) c a_{x'd}(y'd)^c=a_{x'd}(x'd)^c(\frac {y'}{x'})^c axd(yd)c=axd(xd)c(xy)c
预处理 f [ x ′ ] [ m ] = ∑ d = 1 m a x ′ d ( x ′ d ) c f[x'][m]=\sum_{d=1}^ma_{x'd}(x'd)^c f[x][m]=d=1maxd(xd)c以及 i c , 1 i c i^c,\frac 1 {i^c} ic,ic1即可

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define rg register
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=1e6+10,mod=998244353;
int n,c,fans,ans[N];
int a[N],b[N],ib[N];
vector<int>sum[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

int add(int x){return x>=mod?x-mod:x;}
int mul(int x,int y){return 1ll*x*y%mod;}
void up(int &x,int y){x=add(x+y);}
int qpow(int x,int y)
{
    int ret=1;
    for(;y;y>>=1,x=mul(x,x)) 
        if(y&1) ret=mul(ret,x);
    return ret;
}

int gcd(int x,int y){return y?gcd(y,x%y):x;}

int main()
{
    n=read();c=read();
    for(int i=1;i<=n;++i) a[i]=read(),b[i]=qpow(i,c),ib[i]=qpow(b[i],mod-2);
    for(int i=1;i<=n;++i)
    {
        sum[i].pb(0);
        for(int d=1;i*d<=n;++d)
        {
            sum[i].pb(add(sum[i][d-1]+mul(a[i*d],b[i*d])));
        }
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j*i<=n;++j)
        {
            if(gcd(i,j)!=1) continue;
            int m=min(n/i,n/j);
            up(ans[i*j],mul(sum[i][m],mul(b[j],ib[i])));
        }
    }
    for(int i=1;i<=n;++i) fans^=ans[i];
    printf("%d\n",fans);
    return 0;
}
I. Inverse Pair

【题意】

给定一个排列,你可以给其中的某些位置+1,求最少的逆序对数。

n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n2×105

【思路】

一个数加一只会影响到原来比它恰好大1的数。

f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示考虑到 i i i i i i是否+1,那么转移除了原来增加的逆序对个数,再考虑比它小 1 1 1且在它后面的数有没有+1即可。

复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define rg register
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
const int N=2e5+10,M=31;

int n,a[N];
pii b[N];
ll f[N][2];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

struct BIT
{
    #define lowbit(x) (x&(-x))
    int c[N];
    void insert(int x)
    {
        for(;x<N;x+=lowbit(x)) c[x]++;
    }
    int que(int x)
    {
        int ret=0;
        for(;x>0;x-=lowbit(x)) ret+=c[x];
        return ret;
    }
    int query(int l,int r)
    {
        return que(r)-que(l-1);
    }
}bit;

int main()
{
    n=read();
    for(int i=1;i<=n;++i) a[i]=read(),b[i]=mkp(a[i],i);
    sort(b+1,b+n+1);
    for(int i=1;i<=n;++i)
    {
        int p=b[i].se,tmp=bit.query(p+1,n);
        //printf("%d %d\n",p,tmp);
        f[i][0]=min(f[i-1][0]+tmp,f[i-1][1]+tmp-(b[i].se<b[i-1].se));
        f[i][1]=min(f[i-1][1]+tmp,f[i-1][0]+tmp);
        bit.insert(p);
        //printf("%d %lld %lld\n",i,f[i][0],f[i][1]);
    }
    printf("%lld\n",min(f[n][0],f[n][1]));
    return 0;
}
J. Average

【题意】

给定一个长度为 n n n的序列 a i a_i ai和一个长度为 m m m的序列 b i b_i bi,令 W i , j = a i + b j W_{i,j}=a_i+b_j Wi,j=ai+bj,求一个平均值最小的子矩形的值,且要求矩阵的长至少为 x x x,宽至少为 y y y

n , m ≤ 1 0 5 n,m\leq 10^5 n,m105

【思路】

经过简单推导可以得到答案实际上是对 a a a b b b的对应区间分别求个平均值然后加起来,所以问题变成了怎么在一个序列中找到一个平均值最小的长度大于一个给定值的区间。

这是一个典中典的问题,我们二分答案 S S S以后,令 a i ′ = a i − S a'_i=a_i-S ai=aiS,那么问题就变成了求是否有和 > 0 >0 >0的长度至少为 x x x的子区间,这个可以简单 O ( n ) O(n) O(n)计算。

复杂度 O ( n log ⁡ W ) O(n\log W) O(nlogW)

【参考代码】

/*
 * @date:2021-07-26 12:09:41
 * @source:
*/
#include <bits/stdc++.h>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef vector<int> vi;
#define fir first
#define sec second
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) (int)x.size()
#define For(i, x) for (int i = 0; i < (x); ++i)
#define Trav(i, x) for (auto & i : x)
#define pb push_back
template<class T, class G> bool chkMax(T &x, G y) {return y > x ? x = y, 1 : 0;}
template<class T, class G> bool chkMin(T &x, G y) {return y < x ? x = y, 1 : 0;}

const int MAXN = 1e5 + 5;
const double eps = 1e-8;

int A[MAXN];
double Sum[MAXN];

bool check(double avg, int n, int m) {
    for (int i = 1; i <= n; ++i)
        Sum[i] = Sum[i - 1] + A[i] - avg;
    double val = 0;
    for (int i = m; i <= n; ++i) {
        chkMin(val, Sum[i - m]);
        if (Sum[i] >= val) return true;
    }
    return false;
}

double solve(int n, int m) {
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &A[i]);
    }
    double l = 0, r = 1e5;
    while (r - l > eps) {
        double mid = (l + r) / 2;
        if (check(mid, n, m)) l = mid;
        else r = mid;
    }
    return r;
}

int N, M, X, Y;

int main() {
    scanf("%d%d%d%d", &N, &M, &X, &Y);
    double a = solve(N, X);
    double b = solve(M, Y);
    printf("%.7lf\n", a + b);
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值