【计数题】

【计数题】

mod 998244353

Grid Counting

https://codeforces.com/contest/2151/problem/D

题目大意

QQ_1758777718324

思路

注意到恰好一个:列放了就不能再放->每列最多一个
注意到第一行只能放最左边和最右边 (如果上一行没放)第二行必须靠里放两个...
那么每行能放的范围仅有\(i<=mid(j,n-j+1)\)
->那么在\((n+1)/2\)以下的行都不应该有数
->行数从后往前遍历肯定是递增的

代码

int n;
void solve(){
    cin>>n;
    vector<int> a(n+1,0);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=n;i>(n+1)/2;i--){
        if(a[i]>0){
            cout<<0<<endl;
            return;
        }
    }
    //cout<<1<<endl;
    i64 ans=1LL;
    i64 cur=0;
    for(int i=(n+1)/2;i>=1;i--){
        if(i==((n+1)/2) && n%2) cur++;
        else cur+=2;
        if(cur<a[i]){
            cout<<0<<endl;
            return;
        }
        ans=ans*C(cur,a[i])%mod;
        cur-=a[i];
    }
    if(cur!=0){
        cout<<0<<endl;
        return;
    }
    cout<<ans<<endl;
}

权值计算

https://ac.nowcoder.com/acm/contest/120562/H

考虑单点贡献法
一类模型

题目大意

定义f(l,r,s)为s数组在l到r内的前缀数字个数和
求s数组所有子数组的f之和

思路

(1)第一类子问题:定义一个数组的权值为数组中所有数字的总和*
※单点贡献法:
每个数出现多少次在子数组中**
对于\(a_i\),左边有\(i\)种选择,右边有\(n-i+1\)种选择(默认本身算到右边)
那么\(a_i\)的贡献为\(a_i \times i \times (n-i+1)\)

(2)第二类子问题:定义一个数组的权值为数组的不同数字个数
①若数字在数组中只出现1次:贡献即为\(i \times (n-i+1)\)
②若数字在数组中出现多次
先考虑两次的情况:若数字\(x\)出现在2个位置\(l,r\)
对于第一个x:在\(l \times (n-l+1)\)个位置包含了第一个x
对于第二个x:在\(r \times (n-r+1)\)个位置包含了第二个x
由于容斥,我们需要排除同时包含第一和第二个x的位置:\(l \times (n-r+1)\)
那么有结果\(l \times (n-l+1)+r \times (n-r+1)-l \times (n-r+1)=l \times (n-l+1)+(r-l) \times (n-r+1)\)
同理,如果往后加进第三个位置的x为\(rr\),第三个位置的贡献为\((rr-r) \times (n-r+1)\)

(3)回到原问题:求一个x在每个子数组的多少个前缀有贡献
显然:若x在位置l,包含l的子数组的所有右端点>=l的都会有贡献
左端点固定为l,那么右端点的前缀个数应当为\(1,2,...,n-l+1\)->等差数列
*注意这里为什么贡献不会变化:计算的是单点对总答案的贡献,左端点时,该数还没出现过,自然不会有贡献
*为什么是等差数列:单点在数组中出现后,一次贡献为1,扩展右端点,会累计出现次数,所以是\(1,2,...,n-l+1\)

再乘上左端点的个数,得单点答案:r是本次出现的位置,l是上一次出现的位置
\((r-l) \times ((n-r+1+1) \times (n-r+1) \div 2)\)

AC代码

int n;
void solve(){
    cin>>n;
    vector<i64> a(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    map<i64,int> mp;
    i64 ans=0;
    for(int i=1;i<=n;i++){
        i64 l=i-mp[a[i]];
        i64 r=(n-i+1);
        i64 rr=(r+1LL)*r/2LL;
        i64 res=l*rr;
        ans+=res;
        mp[a[i]]=i;
    }
    cout<<ans<<endl;
}

【点贡献法】Lost Civilization (Hard Version)

C1思路请参考栈模拟

https://codeforces.com/contest/2202/problem/C2

题目大意

定义一个数组的\(f(b)\)
image
本题要求所有子段的\(f(b)\)之和

思路

【点贡献法】
沿用C1的思想:
单点对任一线段的贡献为1,算有多少个线段满足该贡献
线段->考虑怎么取左右端点
能消除->贡献+1:对于单点来说要避免+1的地方
右端点:后面的点消除不了前面的点->后面可以全取
左端点:前面第一个能消除它的点的序号+1

AC代码

int n;
void solve(){
    cin>>n;
    vector<i64> a(n+1,0);
    for(int i=1;i<=n;i++) cin>>a[i];
    stack<P64> stk;
    i64 ans=0;
    vector<i64> pre(n+1,0);
    for(int i=n;i>=1;i--){
        while(stk.size() && stk.top().fi==(a[i]+1)){
            P64 t=stk.top();
            pre[t.sc]=i;
            stk.pop();
        }
        stk.push({a[i],i});
    }
    //注意:如果自己本身是被选取的点->贡献一定为1,选取整个线段
    for(int i=1;i<=n;i++){
        ans+=(i-pre[i])*(n-i+1LL);
    }
    cout<<ans<<endl;
}
posted @ 2025-09-25 13:20  White_ink  阅读(5)  评论(0)    收藏  举报