省選模擬賽Round4 Day1 A B C(線段樹勢能分析,線性規劃對偶+最大費用循環流,烷烴計數(Polya定理+多項式))

 

題解

一道典型的線段樹勢能分析題目

我們先來思考一下:一次修改之後,如果要直接在線段樹每個節點上維護出最小值該怎麼做

再思考一下什麼情況不能在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的情況算進去

代碼:無

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章