20230814
A. 哈夫曼树
观察到答案与数组位置无关,只与元素总和有关。设 \(dp_i\) 表示操作 \(i\) 次后的期望代价,则第 \(i\) 次操作相当于从数组中随机抽取 \(i+1\) 个元素加入代价,每个元素的期望贡献为 \(\frac{1}{i+1}\) 。可得转移式: \(dp_i=dp_{i-1}+\sum_\limits{j=1}^n\frac{a_j}{i+1}\)。
每次操作选取的两个数有顺序,所以答案需再乘 \(2\) 。
代码
#include<bits/stdc++.h>
#define int long long
#define inv(x) ((ll)(qpow((x),MOD-2)))
using namespace std;
typedef long long ll;
const int N=1e5+5,MOD=1e9+7;
int n,a[N],sum;
ll dp[N];
ll qpow(ll a,ll b){ll ret=1;for(;b;b>>=1)(b&1)&&(ret=ret*a%MOD),a=a*a%MOD;return ret;}
int32_t main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++) scanf("%lld",a+i),sum+=a[i];
for(int i=1;i<n;i++) dp[i]=(dp[i-1]+sum*inv(i+1)%MOD)%MOD;
for(int i=2;i<=n;i++) dp[n-1]=dp[n-1]*(i*(i-1)/2)%MOD;
printf("%lld",dp[n-1]*2%MOD);
return 0;
}
B. 做游戏
每次将相邻的两个格子加1,想到将图黑白染色转化成二分图,分类讨论:
设黑点个数为 \(c_0\) ,权值和为 \(s_0\) ;白点个数为 \(c_1\) ,权值和为 \(s_1\) ,最终棋盘上数字都变为 \(x\) 。则 \(x=\frac{s_1-s_0}{c_1-c_0}\) 。
- 当 \(c_0\neq c_1\) 时:
可以直接求出 \(x\) 的值,用网络流判断 \(x\) 是否可行即可。 - 当 \(c_0 = c_1\) 时:
- 若 \(s_0\neq s_1\),则无法使黑点、白点总和相同,不存在方案。
- 否则二分找出最小可能的 \(x\) 。
判断棋盘上数字都为 \(x\) 是否可行:
按套路先将所有黑点向源点连边,所有白点向汇点连边,流量设为 \(x-a_{i,j}\) 表示这个点需要操作的次数,接下来将所有点与其相邻点连一条流量 \(INF\) 的边。
设 \(sum=\sum_\limits{i=1}^n \sum_\limits{j=1}^m (x-a_{i,j})\) ,若最大流 \(=sum\) ,则 \(x\) 可行。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
const int N=1605,INF=0x3f3f3f3f3f3f3f3f;
int n,m,s,t,a[N][N],T;
int f[N],cnt=1;
int dep[N],cur[N];
int dx[5]={1,-1},dy[5]={0,0,1,-1};
ll s0,s1,c0,c1;
int id(int x,int y){return x*m+y-m;}
bool ckxy(int x,int y){return x>=1&&x<=n&&y>=1&&y<=m;}
struct Edge{
int to,w,nxt;
Edge(){}
Edge(const int &_t,const int &_w,const int &_n):to(_t),w(_w),nxt(_n){}
}ed[N*20];
void __add(int u,int v,int w){ed[++cnt]=Edge(v,w,f[u]);f[u]=cnt;}
void _add(int u,int v,int w){__add(u,v,w);__add(v,u,0);}
bool bfs(){
memset(dep,0,sizeof(dep));
memcpy(cur,f,sizeof(cur));
queue<int> q;q.emplace(s);dep[s]=1;
while(!q.empty()){
int u=q.front();q.pop();
for(int i=f[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(ed[i].w!=0&&dep[v]==0){
dep[v]=dep[u]+1;
if(v==t) return true;
q.emplace(v);
}
}
}
return false;
}
int dinic(int u,int flow){
if(u==t) return flow;
int rst=flow;
for(int &i=cur[u];i;i=ed[i].nxt){
int v=ed[i].to;
if(ed[i].w!=0&&dep[v]==dep[u]+1){
int k=dinic(v,min(rst,ed[i].w));
if(k==0) dep[v]=-1;
ed[i].w-=k;ed[i^1].w+=k;
if((rst-=k)==0) break;
}
}
if(rst==flow) dep[u]=-1;
return flow-rst;
}
bool ck(ll x){
memset(f,0,sizeof(f));cnt=1;ll ret=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
if(i+j&1) _add(id(i,j),t,x-a[i][j]),ret+=x-a[i][j];
else{
_add(s,id(i,j),x-a[i][j]);
for(int d=0;d<4;d++){
int tx=i+dx[d],ty=j+dy[d];
if(!ckxy(tx,ty)) continue;
_add(id(i,j),id(tx,ty),INF);
}
}
}
for(int flow=0;bfs();) while((flow=dinic(s,INF))) ret-=flow;
return !ret;
}
void solve(){
scanf("%lld%lld",&n,&m);s=N-2;t=s+1;
s0=s1=c0=c1=0;int maxn=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
scanf("%lld",a[i]+j);maxn=max(maxn,a[i][j]);
if(i+j&1) s1+=a[i][j],c1++;
else s0+=a[i][j],c0++;
}
if(c0^c1){
ll x=(s1-s0)/(c1-c0);
printf("%lld\n",(x>maxn&&ck(x)?llabs(x*c1-s1):-1));
return;
}
if(s0^s1)return (void)puts("-1");ll ans=0;
for(ll l=1,r=(ll)2e9,mid=l+r>>1;l<r;mid=l+r>>1){
if(ck(mid)) r=ans=mid;
else l=mid+1;
}
printf("%lld\n",llabs(ans*c1-s1));
}
int32_t main(){
for(scanf("%lld",&T);T--;) solve();
return 0;
}
C. 线段树
每次查询只可能为原数组里的数,且答案为某个区间内的最大值,考虑确定这个区间。
将询问区间 \([L,R]\) 倒过来,若区间 \([l_{R-1},r_{R-1}]\) 与区间 \([l_R,r_R]\) 有交集,则答案区间可由 \([l_R,r_R]\) 向 \([l_{R-1},r_{R-1}]\) 扩展。
倍增维护每个操作区间 \([l_i,r_i]\) 前面所有操作区间中由 \(l_i\) 可扩展到的位置和 \(r_i\) 可扩展到的位置,查询操作直接倍增向前跳找出答案区间求最大值,修改操作直接在原数组的线段树上修改。
计算扩展位置时可以用可持久化线段树或区间覆盖单点查询线段树维护,这里暴力查找可过。
http://www.nfls.com.cn:10611/up/solution/NOI2016十连测3.pdf
代码(理论复杂度 $\mathcal{O}(m^2)$ )
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,m,q,a[N];
int L[N],R[N];
int fal[N][21],far[N][21];
bool ckin(int l,int x,int r){return l<=x&&x<=r;}
struct T{
int l,r,mx;
#define l(x) (tre[x].l)
#define r(x) (tre[x].r)
#define mx(x) (tre[x].mx)
}tre[N<<2];
void init(){
for(int i=1;i<=18;i++) for(int j=1;j<=m ;j++){
fal[j][i]=fal[fal[j][i-1]][i-1];
far[j][i]=far[far[j][i-1]][i-1];
}
}
void pushup(int p){mx(p)=max(mx(p<<1),mx(p<<1|1));}
void build(int p,int l,int r){
l(p)=l;r(p)=r;mx(p)=0;
if(l==r) return (void)(mx(p)=a[l]);
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
}
void upd(int p,int x,int k){
if(l(p)==x&&r(p)==x) return (void)(mx(p)=k);
int mid=l(p)+r(p)>>1;
if(x<=mid) upd(p<<1,x,k);
else upd(p<<1|1,x,k);
pushup(p);
}
int query(int p,int l,int r){
if(l<=l(p)&&r>=r(p)) return mx(p);
int mid=l(p)+r(p)>>1,ret=0;
if(l<=mid) ret=max(ret,query(p<<1,l,r));
if(r>mid) ret=max(ret,query(p<<1|1,l,r));
return ret;
}
int qry(bool op,int l,int p){
if(!op){for(int i=18;~i;i--) if(fal[p][i]>=l) p=fal[p][i];}
else{for(int i=18;~i;i--) if(far[p][i]>=l) p=far[p][i];}
return p;
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++) scanf("%d",a+i);
for(int i=1;i<=m;i++) scanf("%d%d",L+i,R+i);
for(int i=1;i<=m;i++){
for(int j=i-1;j;j--)
if(ckin(L[j],L[i],R[j])){fal[i][0]=j;break;}
for(int j=i-1;j;j--)
if(ckin(L[j],R[i],R[j])){far[i][0]=j;break;}
}
init();build(1,1,n);
for(int op,l,r,k;q--;){
scanf("%d%d%d",&op,&l,&r);if(op==2) scanf("%d",&k);
if(op==1) upd(1,l,r);
if(op==2){
int ql=-1,qr=-1;
for(int i=r;i>=l;i--){
if(!ckin(L[i],k,R[i])) continue;
ql=L[qry(0,l,i)];qr=R[qry(1,l,i)];
break;
}
if(ql==-1&&qr==-1) ql=qr=k;
// printf("ql=%d,qr=%d:\n",ql,qr);
printf("%d\n",query(1,ql,qr));
}
}
return 0;
}

浙公网安备 33010602011771号