题解

一道典型的线段树势能分析题目

我们先来思考一下:一次修改之后,如果要直接在线段树每个节点上维护出最小值该怎么做

再思考一下什么情况不能在O(1)完成对最小值的修改

我们发现,区间与和区间或的操作本质就是拆位之后,对每一位分别做区间覆盖操作

比如某一位上&0,就代表着一段区间要赋为0

某一位上|1,就代表这一段区间要赋为1

而&1、|0操作对序列没有任何影响

于是我们把一次操作的有效位k提取出来

在线段树的节点上,我们维护两个值d0、d1,分别表示这段区间中哪些位全为0,哪些位全为1(状压为二进制存储)

如果k被d0、d1的并集包含,那么我们就可以O(1)修改当前节点,并且打一个下传的标记

O(1)修改分类讨论一下就可以了,最后可以合并两种情况

这里pushdown的作用就是让儿子节点与父亲节点的信息保持一致,也可以做到O(1)

 

那为什么这样做的复杂度是对的呢?

我们拆位来考虑,在最后的时间复杂度乘上一个O(k)即可

那么如果当前区间需要向下走,当且仅当这一个区间不是全0或全1

但是我覆盖之后,这段区间,就一定会变成全0或者全1

而一次覆盖两端的残余块最多会增加logn个需要向下走的区间(即logn的势能)

所以一位的时间复杂度为O(logn)

总的时间复杂度为O(nklogn)

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void gi(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
#define N 500005
#define lc i<<1
#define rc i<<1|1
#define LOG 30
const int INF=2147483647;
int val[N];
struct node{
	int l,r,mi,d[2];
	bool la;
}a[N<<2];
inline void pushup(int i)
{
	a[i].d[0]=a[lc].d[0]&a[rc].d[0];
	a[i].d[1]=a[lc].d[1]&a[rc].d[1];
	a[i].mi=min(a[lc].mi,a[rc].mi);
}
inline void cal(int i,int k,int op)
{
	a[i].la=1;
	int tmp=a[i].d[op^1]&k;
	a[i].d[op^1]^=tmp;
	a[i].d[op]^=tmp;
	a[i].mi^=tmp;
}
inline void pushdown(int i)
{
	if(a[i].l<a[i].r&&a[i].la){
		cal(lc,a[i].d[0],0);
		cal(rc,a[i].d[0],0);
		cal(lc,a[i].d[1],1);
		cal(rc,a[i].d[1],1);
		a[i].la=0;
	}
}
void build(int i,int l,int r)
{
	a[i].l=l;a[i].r=r;
	if(l==r){
		a[i].mi=val[l];int x=val[l];
		for(int j=0;j<=LOG;j++)
			a[i].d[(x>>j)&1]|=(1<<j);
		return;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);build(rc,mid+1,r);
	pushup(i);
}
void insert(int i,int l,int r,int k,int op)
{
	if(l<=a[i].l&&a[i].r<=r&&((a[i].d[0]^a[i].d[1])&k)==k){
		cal(i,k,op);
		return;
	}
	pushdown(i);
	if(l<=a[lc].r)insert(lc,l,r,k,op);
	if(r>=a[rc].l)insert(rc,l,r,k,op);
	pushup(i);
}
inline int query(int i,int l,int r)
{
	if(l<=a[i].l&&a[i].r<=r)return a[i].mi;
	pushdown(i);
	int ret=INF;
	if(l<=a[lc].r)ret=min(query(lc,l,r),ret);
	if(r>=a[rc].l)ret=min(query(rc,l,r),ret);
	return ret;
}
void write(int x){if(x>=10)write(x/10);putchar(x%10+48);}
int main()
{
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	int n,m,i,l,r,x,op;
	gi(n);gi(m);
	for(i=1;i<=n;i++)gi(val[i]);
	build(1,1,n);
	while(m--){
		gi(op);gi(l);gi(r);
		if(op==3){
			write(query(1,l,r));
			putchar('\n');
		}
		else{
			gi(x);if(op==1)x^=INF;
			insert(1,l,r,x,op-1);
		}
	}
}

 

 

 

 

 

题解

一开始去想DP了

结果考试结束之后才发现自己想假了

因为它的前驱有重复的点,所以就可能会重复计算某个点的代价

这样例迷惑性好强啊,给的是一棵树。。。这种做法竟然还过了样例。。。

还是来看正解吧

首先有几个概念

原函数:c^T*x

原不等式:Ax>=b、-x>=-t

原变量:x

原函数中变量的系数:c^T

原不等式中变量的系数:A、-1

原不等式的参数:b、-t

 

听Freopen说,对偶问题就是

最小化 变 最大化

一个 原不等式 看成一个 新变量:(Ax>=b -----> y) ( -x>=-t  ----->z)

然后最大化这些新变量的值

这些   新函数中变量的系数  就是   原不等式的参数的转置

b^T、-t^T       (注意,题解中的式子写错了,应该是b^T*y - t^T*z)

对每一个原变量写出一个新的不等式

这些   新不等式的参数  就是   原函数中变量的系数的转置(大于小于符号反向)

?????<=c

这些   新不等式中变量的系数   就是  原不等式中变量的系数的转置

A^T*y - z <=c

注意,x在不同的不等式中对应的系数,写在新不等式中也会对应不同的变量

终于搞完了。。。

然后就是一个最大费用循环流问题???

额,我没看出来。。。

写代码还是会的

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
#define N 205
#define M 10005
#define LL long long
int fir[N],cur[N],to[M],nxt[M],cnt;
LL cap[M],cst[M];
void adde(int a,int b,LL c,LL d)
{
	to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;cap[cnt]=c;cst[cnt]=d;
	to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;cap[cnt]=0;cst[cnt]=-d;
}
int S,T,SS,TT,sz;
LL deg[N];
LL mic,flow,pc;
LL dis[N];bool vis[N];
queue<int> q;bool inq[N];
const LL INF=0x3f3f3f3f3f3f3f3fll;
bool spfa()
{
	for(int i=1;i<=sz;i++)dis[i]=INF;
	q.push(TT);inq[TT]=1;dis[TT]=0;
	while(!q.empty()){
		int u=q.front();q.pop();inq[u]=0;
		for(int v,p=fir[u];p;p=nxt[p]){
			v=to[p];LL w=cst[p^1];
			if(cap[p^1]>0&&dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				if(!inq[v])q.push(v),inq[v]=1;
			}
		}
	}
	return dis[SS]!=INF;
}
LL sap(int u,LL aug)
{
	if(u==TT){mic+=aug*dis[SS];return aug;}
	int tmp,ret=0;
	vis[u]=1;
	for(int v,&p=cur[u];p;p=nxt[p]){
		v=to[p];
		if(!vis[v]&&cap[p]>0&&dis[u]==dis[v]+cst[p]){
			tmp=sap(v,min(aug,1ll*cap[p]));
			cap[p]-=tmp;aug-=tmp;
			cap[p^1]+=tmp;ret+=tmp;
			if(aug==0)break;
		}
	}
	vis[u]=0;
	return ret;
}
void micflow()
{
	flow=mic=0;
	while(spfa()){
		for(int i=1;i<=sz;i++)cur[i]=fir[i];
		flow+=sap(SS,INF);
	}
}
int t[N],c[N];
int main()
{
	freopen("b.in","r",stdin);
	freopen("b.out","w",stdout);
	cnt=1;
	int n,m,W,i,u,v;
	scanf("%d%d%d",&n,&m,&W);
	for(i=1;i<=n;i++)scanf("%d",&t[i]);
	for(i=1;i<=n;i++)scanf("%d",&c[i]);
	S=2*n+1;T=S+1;SS=T+1;TT=SS+1;sz=TT;
	for(i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		adde(u+n,v,INF,0);
	}
	for(i=1;i<=n;i++){
		pc+=1ll*t[i]*c[i];
		deg[i+n]+=c[i],deg[i]-=c[i];
		adde(i+n,i,c[i],t[i]);
		adde(S,i,INF,0);
		adde(i,i+n,INF,0);
		adde(i+n,T,INF,0);
	}
	for(i=1;i<=sz;i++){
		if(deg[i]>0)adde(SS,i,deg[i],0);
		else adde(i,TT,-deg[i],0);
	}
	adde(T,S,INF,0);
	int l=0,r=55005,mid;
	while(l<r){
		mid=(l+r)>>1;
		for(i=2;i<=cnt;i+=2)
			cap[i]+=cap[i^1],cap[i^1]=0;
		cst[cnt-1]=mid;cst[cnt]=-mid;
		micflow();
		if(pc-mic>W)l=mid+1;
		else r=mid;
	}
	printf("%d\n",l);
}

 

 

 

T3是烷烃计数

看懂了,但是并不会牛顿迭代,也不会分治NTT

这里的Polya定理是任意置换所有子树,所以一共有6种置换

这个p-q+s=1的本质还是边点容斥,P(x)中由于是算的点等价类个数,不能把0的情况算进去

代码:无