20200612 hz【矩形容斥,链表+回滚莫队,保序回归L1】

6 篇文章 0 订阅
1 篇文章 0 订阅

T1:「雅礼集训 2018 Day11」进攻!

题目描述:

在这里插入图片描述
n , m ≤ 2000 , k ≤ 1 0 6 n,m\le2000,k\le10^6 n,m2000,k106
LOJ link

题目分析:

求选K个全1矩形使其有交的方案数。
考虑容斥,计算每个 1 × 1 1\times 1 1×1的矩形被多少个矩形包含,设为 s s s,那么答案加上 s k s^k sk
然后看多算了什么,假设某个方案中矩形的交的大小是一个 x ∗ y x*y xy的矩形,那么它被算了 x ∗ y x*y xy次。
减去 1 × 2 1\times 2 1×2的矩形, 2 × 1 2\times 1 2×1的矩形,加上 2 × 2 2\times 2 2×2的矩形。
那么一个交为 x ∗ y x*y xy的方案被算了 x ∗ y − x ( y − 1 ) − y ( x − 1 ) + ( x − 1 ) ( y − 1 ) = 1 x*y-x(y-1)-y(x-1)+(x-1)(y-1)=1 xyx(y1)y(x1)+(x1)(y1)=1 次。
原理可以理解为 点 - 边 + 环 = 1,矩形是一个天然的简单环结构。

那么问题就是计算 1 / 2 × 1 / 2 1/2\times 1/2 1/2×1/2 的矩形被多少个全1矩形包含。

先考虑 1 × 1 1\times 1 1×1
一个全1矩形会对内部的点有1的贡献,差分之后就是四个角+1或-1,但是矩形并不是输入的,不太好分开一个一个加。
求出以每个点为右下角的全1矩形个数,就可以算出右下角的+1的差分值;同理,求出以每个点为左上/左下/右下的全1矩形个数,就可以算出每个位置的差分值。最后求前缀和就可以得到每个点被覆盖的次数。

对于其它情况类似。求以每个点为右下角的全1矩形个数可以单调队列(延伸作限制,维护单调上升的立柱)
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
Code:

#include<bits/stdc++.h>
#define maxn 2005
#define rep(i,j,k) for(int i=(j),lim=(k);i<=lim;i++)
using namespace std;
const int mod = 998244353;
int n,m,K,s[maxn][maxn],f[maxn][maxn],d[maxn][maxn][4],ans;
char a[maxn][maxn];
int q[maxn],tp;
int Pow(int a,int b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
void solve(){
	rep(i,1,n){
		int res=0; tp=0;
		rep(j,1,m){
			s[i][j]=a[i][j]=='1'?s[i-1][j]+1:0;
			for(;tp&&s[i][q[tp]]>=s[i][j];tp--) res-=(q[tp]-q[tp-1])*s[i][q[tp]];
			res+=(j-q[tp])*s[i][j], q[++tp]=j;
			f[i][j]=res;
		}
	}
}
int main()
{
	//freopen("attack.in","r",stdin);
	//freopen("attack.out","w",stdout);
	scanf("%d%d%d",&n,&m,&K);
	for(int i=1;i<=n;i++) scanf("%s",a[i]+1);
	
	solve();
	rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+1][j+1][k]=f[i][j];
	
	rep(i,1,n) reverse(a[i]+1,a[i]+1+m);
	solve();
	rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+1][j+(k&1)][k]-=f[i][m-j+1];
	
	reverse(a+1,a+1+n);
	solve();
	rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+(k>=2)][j+(k&1)][k]+=f[n-i+1][m-j+1];
	
	rep(i,1,n) reverse(a[i]+1,a[i]+1+m);
	solve();
	rep(i,1,n) rep(j,1,m) rep(k,0,3) d[i+(k>=2)][j+1][k]-=f[n-i+1][j];
	
	rep(i,1,n) rep(j,1,m) rep(k,0,3)
		d[i][j][k]=(1ll*d[i][j][k]+d[i-1][j][k]+d[i][j-1][k]-d[i-1][j-1][k])%mod,
		ans=(ans+(!k||k==3?1:-1)*Pow(d[i][j][k],K))%mod;
	printf("%d\n",(ans+mod)%mod);
}

T2:「雅礼集训 2018 Day11」字符串

在这里插入图片描述
LOJ6517

N , M ≤ 1 0 5 , ∑ ∣ s i ∣ ≤ 3 ∗ 1 0 5 N,M\le10^5,\sum|s_i|\le3*10^5 N,M105,si3105

题目分析:

莫队。初步尝试用 Trie树 + set 维护答案
直接对 n n n 莫队的话,可能每次都加入一个很长的串。

发现串总长 ≤ 3 ∗ 1 0 5 \le 3*10^5 3105,即每个串的前缀的个数 ≤ 3 ∗ 1 0 5 \le 3*10^5 3105
那么把串接起来,记录每个串的 s t a r t , e n d start,end start,end,原先对 [ l , r ] [l,r] [l,r]的询问就相当于将 [ s t l , e d r ] [st_l,ed_r] [stl,edr]的点对应前缀加入后的答案。于是可以对此莫队。复杂度 O ( ( ∑ ∣ s i ∣ ) ∗ m log ⁡ m a x l e n ) O\left((\sum|s_i|)*\sqrt m\log maxlen\right) O((si)m logmaxlen)

考虑把 s e t set set换成链表,但是链表只支持删除,不支持插入(没法找插入前驱后继),但是它支持删除后按顺序回退,所以用回滚莫队,初始时全部加入。每次把左指针指到块左端点,右指针由大到小删除,最后回退即可。

注意维护答案的时候没有出现的前缀可能 A ∗ l e n ( S ) ≥ C A*len(S)\ge C Alen(S)C,但是不能算进去,要判断 f ( S ) f(S) f(S)是否为空(数据没有 v i = 0 v_i=0 vi=0的情况)

Code:

#include<bits/stdc++.h>
#define maxn 300005
#define LL long long
using namespace std;
void read(int &a){
	char c;while(!isdigit(c=getchar()));
	for(a=c-'0';isdigit(c=getchar());a=a*10+c-'0');
}
const int S = 900;
int n,m,A,B,C,W[maxn],sum,mxl,g[maxn],pos[maxn],st[maxn],ed[maxn],bel[maxn];
LL ans[maxn],res,f[maxn];
struct node{
	int l,r,id;
	bool operator < (const node &p)const{return bel[l]==bel[p.l]?r>p.r:bel[l]<bel[p.l];}
}q[maxn];
char s[maxn];
int ch[maxn][26],sz,w[maxn],len[maxn],cnt[maxn],L[maxn],R[maxn];
void insert(char *s,int len,int val){
	int r=0,v;
	for(int i=0;i<len;i++){
		if(!ch[r][v=s[i]-'a']) ch[r][v]=++sz,::len[sz]=i+1;
		pos[++sum]=r=ch[r][v],w[sum]=val;
	}
}
LL F(int x){return 1ll*x*(x-1);}
bool chk(int x){return !f[x]?0:1ll*B*f[x]+1ll*A*len[x]>=C;}
void ins(int x){
	int p=pos[x],pre=chk(p); f[p]+=w[x];
	if(!pre&&chk(p)&&!cnt[g[len[p]]]++)
		p=g[len[p]],res+=F(p-L[p])+F(R[p]-p)-F(R[p]-L[p]),R[L[p]]=L[R[p]]=p;
}
void del(int x){
	int p=pos[x],pre=chk(p); f[p]-=w[x];
	if(pre&&!chk(p)&&!--cnt[g[len[p]]])
		p=g[len[p]],res+=F(R[p]-L[p])-F(p-L[p])-F(R[p]-p),R[L[p]]=R[p],L[R[p]]=L[p];
}
int main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	read(n),read(A),read(B),read(C);
	for(int i=1;i<=n;i++) read(W[i]);
	for(int i=1;i<=n;i++){
		scanf("%s",s); int len=strlen(s); mxl=max(mxl,len);
		st[i]=sum+1,ed[i]=sum+len,insert(s,len,W[i]);
	}
	for(int i=1;i<=mxl;i++) read(g[i]);
	for(int i=1;i<=sum;i++) bel[i]=(i-1)/S;
	read(m);
	for(int i=1,x,y;i<=m;i++) read(x),read(y),q[i].l=st[x],q[i].r=ed[y],q[i].id=i;
	for(int i=1;i<=sum;i++) f[pos[i]]+=w[i];
	for(int i=1;i<=sz;i++) if(chk(i)) cnt[g[len[i]]]++;
	int last=0;
	for(int i=1;i<=mxl;i++) if(cnt[i]) R[L[i]=last]=i,res+=F(i-last),last=i;
	R[last]=mxl+1,res+=F(R[last]-last);
	sort(q+1,q+1+m);
	for(int k=0,i=1;k*S+1<=sum&&i<=m;k++){
		int lb=k*S+1,rb=min((k+1)*S+1,sum+1),x=lb,y=sum;
		for(;i<=m&&bel[q[i].l]==k;i++){
			while(y>q[i].r) del(y--);
			while(x<q[i].l) del(x++);
			ans[q[i].id]=res;
			while(x>lb) ins(--x);
		}
		while(y<sum) ins(++y);
		while(x<rb) del(x++);
	}
	for(int i=1;i<=m;i++){
		LL y=F(mxl+1),x=y-ans[i],d=__gcd(x,y);
		printf("%lld/%lld\n",x/d,y/d);
	}
}

T3:「雅礼集训 2018 Day11」序列

在这里插入图片描述
在这里插入图片描述
LOJ6518

N ≤ 5000 , M ≤ 15000 , a i ≤ 1 0 5 N\le5000,M\le15000,a_i\le10^5 N5000,M15000,ai105

题目分析:

偏序关系,最小化 ∑ ∣ x i − a i ∣ \sum |x_i-a_i| xiai,弱化的保序回归 L 1 L_1 L1问题,整体二分,每次求出向 S { m i d , m i d + 1 } S\{mid,mid+1\} S{mid,mid+1}取整的最优解,然后划分。详见 IOI2018集训队论文《浅谈保序回归问题》。
实际上代价是 ( x i − a i ) k (x_i-a_i)^k (xiai)k都可以这么做,最小割中划到 m i d mid mid的点选择 m i d + 1 mid+1 mid+1的代价为原点走到 m i d + 1 mid+1 mid+1的代价减去原点走到 m i d mid mid的代价。2020年省选联考A卷T3就是保序回归问题,发现线性基的替换结论得到偏序之后就是模板了。(然而省选一周前写过这道题的我并不知道原来这个做法可以做 k k k次方…)

Code:

#include<bits/stdc++.h>
#define maxn 50005
#define maxm 500005
#define pb(x) push_back(x)
#define rep(i,j,k) for(int i=(j),lim=(k);i<=lim;i++)
using namespace std;
const int inf = 0x3f3f3f3f;
int n,m,a[maxn],ans;
struct node{int l,r,t;};
vector<node>Q[maxn];
int S,T,dis[maxn];
int fir[maxn],cur[maxn],nxt[maxm],to[maxm],c[maxm],tot=1;
void line(int x,int y,int z=inf){
	nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,c[tot]=z;
	nxt[++tot]=fir[y],fir[y]=tot,to[tot]=x,c[tot]=0;
}
#define lc i<<1
#define rc i<<1|1
int id[maxn],tr[maxn][2],sz;
void build(int i,int l,int r){
	if(l==r) return void(tr[i][0]=tr[i][1]=id[l]);
	int mid=(l+r)>>1;
	tr[i][0]=++sz,tr[i][1]=++sz;
	build(lc,l,mid),build(rc,mid+1,r);
	line(tr[i][0],tr[lc][0]),line(tr[i][0],tr[rc][0]);
	line(tr[lc][1],tr[i][1]),line(tr[rc][1],tr[i][1]);
}
void ins(int i,int l,int r,int x,int y,int p,int t){
	if(x<=l&&r<=y) {!t?line(p,tr[i][0]):line(tr[i][1],p); return;}
	int mid=(l+r)>>1;
	if(x<=mid) ins(lc,l,mid,x,y,p,t);
	if(y>mid) ins(rc,mid+1,r,x,y,p,t);
}
bool BFS(){
	static int q[maxn],l,r;
	memset(dis,-1,(sz+1)<<2),dis[q[l=r=1]=T]=0;
	while(l<=r){
		int u=q[l++];
		for(int i=fir[u],v;i;i=nxt[i]) if(c[i^1]&&dis[v=to[i]]==-1)
			dis[q[++r]=v]=dis[u]+1;
	}
	return ~dis[S];
}
int aug(int u,int augco){
	if(u==T) return augco;
	int need=augco,dt;
	for(int &i=cur[u];i;i=nxt[i]) if(c[i]&&dis[u]==dis[to[i]]+1){
		dt=aug(to[i],min(c[i],need)),c[i]-=dt,c[i^1]+=dt;
		if(!(need-=dt)) return augco;
	}
	return augco-need;
}
void solve(int l,int r,vector<int>&A){
	if(l==r) {for(int x:A) ans+=abs(a[x]-l); return;}
	if(A.empty()) return;
	if(A.size()==1) {int x=A[0]; ans+=max(a[x]-R,0)+max(L-a[x],0); return;}
	int mid=(l+r)>>1;
	S=sz=1,T=++sz;
	rep(i,0,A.size()-1) id[i]=++sz;
	build(1,0,A.size()-1);
	rep(i,0,A.size()-1)
		for(node q:G[A[i]]){
			int x=lower_bound(A.begin(),A.end(),q.l)-A.begin(),y=upper_bound(A.begin(),A.end(),q.r)-A.begin()-1;
			if(x<=y) ins(1,0,A.size()-1,x,y,id[i],q.t);
		}
	rep(i,0,A.size()-1) a[A[i]]<=mid ? line(id[i],T,1) : line(S,id[i],1);
	while(BFS()) memcpy(cur,fir,(sz+1)<<2),aug(S,inf);
	BFS();
	vector<int>L,R;
	rep(i,0,A.size()-1) dis[id[i]]!=-1 ? L.pb(A[i]) : R.pb(A[i]);
	memset(fir,0,(sz+1)<<2),tot=1;
	solve(l,mid,L),solve(mid+1,r,R);
}
int main()
{
	//freopen("sequence.in","r",stdin);
	//freopen("sequence.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int x;m--;) {node q; scanf("%d%d%d%d",&q.t,&q.l,&q.r,&x),Q[x].pb(q);}
	vector<int>A;
	for(int i=1;i<=n;i++) A.pb(i);
	solve(*min_element(a+1,a+1+n),*max_element(a+1,a+1+n),A);
	printf("%d\n",ans);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值