文章目录
- [C 贪心 策略](#C 贪心 策略)
- [D 组合数学 容斥原理](#D 组合数学 容斥原理)
- [E 状压 绝对值 贪心](#E 状压 绝对值 贪心)
C 贪心 策略

基本策略:操作1改小的,让大的数进行操作2变成小的
cpp
void solve(){
int n,k;cin>>n>>k;
vector<int>a(n+1),pre(n+1,0);
int sm=0;
forr(i,1,n)cin>>a[i];
sort(a.begin()+1,a.end());
forr(i,1,n){
pre[i]=pre[i-1]+a[i];
}
if(pre[n]+a[1]<=k)cout<<0<<endl;
else{
int mn=inf;
reforr(i,1,n){
int aftsm=k-pre[i]+a[1];// 修改的部分最后得到的和
int aim;
// aim*(n-i+1)<=aftsm
if(aftsm>=0)aim=aftsm/(n-i+1);// aim是a[1]要改成的数
else aim=(aftsm-n+i)/(n-i+1);// 负数向下取整
mn=min(mn,max(a[1]-aim,0ll)+n-i);
}
cout<<mn<<endl;
}
}
D 组合数学 容斥原理

cpp
const int N = 1e5,M=1e5;
const double PI=acos(-1);
const long long mod =998244353, inf = 1e9+10 ;
int fac[N+10],ifac[N+10];
int qpow(int a,int b,int p){
int res=1;
while (b)
{
if(b&1)(res*=a)%=p;
b>>=1;
(a*=a)%=p;
}
return res%p;
}
int inv(int x){
return qpow(x,mod-2,mod)%mod;
}
void init(){
fac[0]=1;
forr(i,1,N){
fac[i]=fac[i-1]*i%mod;
}
ifac[N]=inv(fac[N]);
reforr(i,0,N-1){
ifac[i]=ifac[i+1]*(i+1)%mod;
}
}
int C(int n,int m){
return fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
void solve(){
int n,k;
string s;cin>>n>>k>>s;
s=' '+s;
/*
双指针 拓展最长的有k个1的段
任意取的最长段 1的数量都不变
*/
vector<int>pre(n+1,0);
forr(i,1,n){
pre[i]=pre[i-1]+(s[i]-'0');
}
if(pre[n]<k||k==0)return cout<<1<<endl,void();// 不操作
// 该题 问的是最多进行一次操作 不操作也可以得到1
// ICPC南昌邀请赛I 问的是进行一次操作
int ans=0;
if(k==1){
vector<int>cnt;
int z=0;
forr(i,1,n){
if(s[i]=='1'){
cnt.push_back(z);
z=0;
if(cnt.size()>1){
int tp=cnt.size();
ans+=cnt[tp-1]+cnt[tp-2];
}
}else z++;
}
cnt.push_back(z);
int tp=cnt.size();
ans+=cnt[tp-1]+cnt[tp-2]+1;// 进行一次操作也可以不修改
}else{
int nowl=1,nowr=0,lstr=-1;
while (nowr<n)
{
while(nowr<n&&pre[nowr+1]-pre[nowl-1]<=k)nowr++;
if(pre[nowr]-pre[nowl-1]<k)break;// 判断区间1的个数
int lap=0;// 和上一段重叠部分
if(lstr>=nowl)lap=C(lstr-nowl+1,pre[lstr]-pre[nowl-1]);// nowl~lstr这一段的1数量不变 就是两段都会算的部分
(ans+=C(nowr-nowl+1,pre[nowr]-pre[nowl-1])-lap+mod)%=mod;
while(nowl<=nowr&&pre[nowr]-pre[nowl-1]>=k)nowl++;// 摆脱1数量=k的这一段
lstr=nowr;
}
}
cout<<ans<<endl;
}
E 状压 绝对值 贪心

题意: r [ i ] = ∑ j = 1 m p [ j ] s [ i ] [ j ] r[i] = \sum_{j = 1} ^m p[j] s[i][j] r[i]=∑j=1mp[j]s[i][j]。给定 x[i] ,问 ∑ i = 1 n ∣ r [ i ] − x [ i ] ∣ \sum_{i = 1}^n |r[i] - x[i]| ∑i=1n∣r[i]−x[i]∣ 的最大值
无论实际情况如何 ,总有: ∣ r i − x i ∣ = sign i ⋅ ( r i − x i ) |r_i - x_i| = \text{sign}_i \cdot (r_i - x_i) ∣ri−xi∣=signi⋅(ri−xi)其中 sign i \text{sign}_i signi 的取值取决于 r i r_i ri 和 x i x_i xi 的大小关系。
由于 n ≤ 10 n \leq 10 n≤10,我们可以枚举所有 2 n 2^n 2n 种符号组合 ( sign 1 , sign 2 , ... , sign n ) (\text{sign}_1, \text{sign}_2, \ldots, \text{sign}_n) (sign1,sign2,...,signn)。
对于固定的符号组合 ,目标函数变为:
∑ i = 1 n sign i ( r i − x i ) = ∑ i = 1 n sign i r i − ∑ i = 1 n sign i x i \sum_{i=1}^n \text{sign}i(r_i - x_i) = \sum{i=1}^n \text{sign}i r_i - \sum{i=1}^n \text{sign}_i x_i i=1∑nsigni(ri−xi)=i=1∑nsigniri−i=1∑nsignixi
这里的关键是:我们不是在猜测实际的符号,而是在尝试所有可能的符号分配方式 。并且符合 r i r_i ri 和 x i x_i xi 的大小关系的符号一定能取到最大值。
因此可以把绝对值符号去掉。
对于每一种符号组合,我们计算:
- 固定值: − ∑ i = 1 n sign i x i -\sum_{i=1}^n \text{sign}_i x_i −∑i=1nsignixi(这部分与 p p p 无关)
- 可变值: ∑ i = 1 n sign i r i = ∑ j = 1 m p j ( ∑ i = 1 n sign i s i , j ) \sum_{i=1}^n \text{sign}i r_i = \sum{j=1}^m p_j \left(\sum_{i=1}^n \text{sign}i s{i,j}\right) ∑i=1nsigniri=∑j=1mpj(∑i=1nsignisi,j)
然后我们通过排序找到使可变值最大的排列 p p p。
cpp
void solve(){
int n,m;cin>>n>>m;
vector<int>x(n+1);vector<string>s(n+1);
forr(i,1,n)cin>>x[i];
forr(i,1,n)cin>>s[i];
int mx=-1;
vector<int>ans;
/*
因为枚举正负号,绝对值的情况被包含在内,并且肯定是最大值
如果枚举的正负号不贴合去掉绝对值的情况,必然得不到最大值
*/
forr(b,0,(1<<n)-1){// 状压
// 枚举正负符号情况 1:r_i>x_i |r_i-x_i|=r_i-x_i 0:r_i<x_i |r_i-x_i|=-r_i+x_i
vector<int>tp(m+1);
vector<pii>v(m+1);// 每个位置的系数 需要记录下标
forr(j,1,m)v[j]={0,j};
int xsm=0;
forr(i,1,n){
if(b>>(i-1)&1){
xsm-=x[i];
forr(j,1,m){
if(s[i][j-1]-'0')v[j].fir++;
}
}else{
xsm+=x[i];
forr(j,1,m){
if(s[i][j-1]-'0')v[j].fir--;
}
}
}
// cout<<"xsm"<<xsm<<endl;
// 因为是排列 待填p_i是固定的 系数大的分配大的p_i
sort(v.begin()+1,v.end());
int rsm=0;
forr(j,1,m){
tp[v[j].sec]=j;
rsm+=j*v[j].fir;
}
// cout<<"rsm"<<rsm<<endl;
if(rsm+xsm>mx){
mx=rsm+xsm;
ans=tp;
}
}
forr(i,1,m)cout<<ans[i]<<' ';cout<<endl;
}