【计数题】
【计数题】
mod 998244353
Grid Counting
https://codeforces.com/contest/2151/problem/D
题目大意

思路
注意到恰好一个:列放了就不能再放->每列最多一个
注意到第一行只能放最左边和最右边 (如果上一行没放)第二行必须靠里放两个...
那么每行能放的范围仅有\(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)\)为

本题要求所有子段的\(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;
}

浙公网安备 33010602011771号