题解
一道典型的线段树势能分析题目
我们先来思考一下:一次修改之后,如果要直接在线段树每个节点上维护出最小值该怎么做
再思考一下什么情况不能在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的情况算进去
代码:无