太长了,分开来发博客
20200522B农民
题意:有一棵权值不合法的二叉搜索树,带权值修改,子树翻转,查询点u在访问其权值a[u]是能否被找到
题解:
一个点u能在查询a[u]时被访问到,必须满足由根到它的路径上的祖先的权值的大小范围限制
把每一个点的权值看做分别对左右子树的限制,维护小于限制的最大值与大于限制的最小值,以及翻转后的这两个值
有一定的细节:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
int fa[N],ch[N][2];
int son[N],siz[N],top[N];
int dfn[N],num[N],dc;
void dfs1(int u)
{
siz[u]=1;
for(int v,i=0;i<2;i++){
if((v=ch[u][i])){
dfs1(v);siz[u]+=siz[v];
if(siz[son[u]]<siz[v])son[u]=v;
}
}
}
void dfs2(int u)
{
dfn[u]=++dc;num[dc]=u;
if(son[u])top[son[u]]=top[u],dfs2(son[u]);
for(int v,i=0;i<2;i++)
if((v=ch[u][i])&&v!=son[u])
top[v]=v,dfs2(v);
}
#define lc i<<1
#define rc i<<1|1
const int INF=0x3f3f3f3f;
int val[N],mx[N<<2][2],mi[N<<2][2];bool rev[N<<2];
struct node{int l,r;}a[N<<2];
void pushup(int i)
{
mi[i][0]=min(mi[lc][0],mi[rc][0]);
mx[i][0]=max(mx[lc][0],mx[rc][0]);
mi[i][1]=min(mi[lc][1],mi[rc][1]);
mx[i][1]=max(mx[lc][1],mx[rc][1]);
}
void rever(int i)
{
rev[i]^=1;
swap(mi[i][0],mi[i][1]);
swap(mx[i][0],mx[i][1]);
}
void pushdown(int i)
{
if(rev[i]){
rever(lc),rever(rc);
rev[i]=0;
}
}
void build(int i,int l,int r)
{
a[i].l=l;a[i].r=r;
mi[i][0]=mi[i][1]=INF;
mx[i][0]=mx[i][1]=-INF;
if(l==r){
int u=num[l];
if(fa[u]){
if(ch[fa[u]][0]==u)
mi[i][0]=mx[i][1]=val[fa[u]];
else
mx[i][0]=mi[i][1]=val[fa[u]];
}
return;
}
int mid=(l+r)>>1;
build(lc,l,mid);build(rc,mid+1,r);
pushup(i);
}
void fresh(int i,int x)
{
if(a[i].l>x||a[i].r<x)return;
if(a[i].l==a[i].r){
int u=num[x];
if(fa[u]){
mi[i][0]=mi[i][1]=INF;
mx[i][0]=mx[i][1]=-INF;
if((ch[fa[u]][0]==u)^rev[i])
mi[i][0]=mx[i][1]=val[fa[u]];
else
mx[i][0]=mi[i][1]=val[fa[u]];
}
return;
}
pushdown(i);
fresh(lc,x);fresh(rc,x);
pushup(i);
}
void modify(int x,int k)
{
val[x]=k;
if(ch[x][0])fresh(1,dfn[ch[x][0]]);
if(ch[x][1])fresh(1,dfn[ch[x][1]]);
}
void insert(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return;
if(l<=a[i].l&&a[i].r<=r){rever(i);return;}
pushdown(i);
insert(lc,l,r);insert(rc,l,r);
pushup(i);
}
int X;
bool query(int i,int l,int r)
{
if(a[i].l>r||a[i].r<l)return 1;
if(l<=a[i].l&&a[i].r<=r)
return (X>mx[i][0]&&X<mi[i][0]);
pushdown(i);
return query(lc,l,r)&query(rc,l,r);
}
int rt;
bool QP(int x)
{
bool ret=1;X=val[x];
while(x){
ret&=query(1,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
return ret;
}
int main()
{
int n,Q,i,op,x,k;
n=gi();Q=gi();
for(i=1;i<=n;i++){
val[i]=gi();ch[i][0]=gi();ch[i][1]=gi();
if(ch[i][0])fa[ch[i][0]]=i;
if(ch[i][1])fa[ch[i][1]]=i;
}
for(i=1;i<=n;i++)if(!fa[i]){rt=i;break;}
dfs1(rt);top[rt]=rt;dfs2(rt);
build(1,1,n);
while(Q--){
op=gi();x=gi();
if(op==1){k=gi();modify(x,k);}
else if(op==2)insert(1,dfn[x]+1,dfn[x]+siz[x]-1);
else{
if(QP(x))printf("YES\n");
else printf("NO\n");
}
}
}
20200523C、秘密行动
题解:
看了好久的题才算看懂了
取对数+最小割
把10个质数分别拆成50个点向源点汇点连边,边权为c、d,表示它是否存在于a[i]中
m组关系实际上就是在某两个点之间连一条权值为f的边
要求乘积最小可以先把边权取log,做完最大流之后exp回去即可
代码:
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 505
#define M 50005
int fir[N],to[M],nxt[M],cnt;
double cap[M],f[15];
void adde(int a,int b,double c,double d)
{
to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;cap[cnt]=c;
to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;cap[cnt]=d;
}
int S,T,sz;
int d[N],vd[N];
const double eps=1e-6;
double sap(int u,double aug)
{
if(u==T)return aug;
int mind=sz-1;double ret=0,tmp;
for(int v,p=fir[u];p;p=nxt[p]){
if(cap[p]>0){
v=to[p];
if(d[u]==d[v]+1){
tmp=sap(v,min(aug,cap[p]));
cap[p]-=tmp;aug-=tmp;
cap[p^1]+=tmp;ret+=tmp;
if(d[S]>=sz)break;
if(aug==0)return ret;
}
mind=min(mind,d[u]);
}
}
if(fabs(ret)<eps){
vd[d[u]]--;
if(vd[d[u]]==0)
d[S]=sz;
d[u]=mind+1;
vd[d[u]]++;
}
return ret;
}
double flow;
void maxflow()
{
memset(d,0,sizeof(d));
memset(vd,0,sizeof(vd));
vd[0]=sz;
while(d[S]<sz)
flow+=sap(S,1e9);
}
int main()
{
cnt=1;
int n,m,i,j,u,v,c,x;
double w;
scanf("%d%d",&n,&m);
for(i=1;i<=10;i++)
scanf("%d%lf",&x,&f[i]);
S=n*10+1;T=n*10+2;sz=T;
for(i=1;i<=n;i++){
u=i;
for(j=1;j<=10;j++){
scanf("%lf",&w);
adde(S,(u-1)*10+j,log(w),0);
}
for(j=1;j<=10;j++){
scanf("%lf",&w);
adde((u-1)*10+j,T,log(w),0);
}
}
for(i=1;i<=m;i++){
scanf("%d%d%d",&u,&v,&c);
adde((u-1)*10+c,(v-1)*10+c,log(f[c]),log(f[c]));
}
maxflow();
printf("%.5f\n",exp(flow));
}
20200525A. 数据结构
题意:一个长度为n的序列,带修改,查一些区间的和的历史最小值,可以离线
题解:
我们一般可以用线段树来解决区间加、单点(实际上是线段树上所有节点对应的区间)查询历史最小值
如果把询问离线下来,把一个询问l,r看成一个坐标为(l,r)的点
那么一个修改就是在一个矩形区域((1,l),(r,n))里修改,单点查询历史最小值
可以想到用KD树来解决
在KD树上的节点维护四个标记minx,miad,val,ad
分别表示该点对应的历史最小值,历史最小增量(历史最小的ad),当前的实际值,加法标记
那么更新方法是显然的
minx=min(minx,val+pmiad)
miad=min(miad,ad+pmiad)
val+=pad ad+=ad
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
#define LL long long
#define lc ch[i][0]
#define rc ch[i][1]
int ch[N][2],fa[N];
int a[N][2],mx[N][2],mi[N][2];
int D,tmp[N],rt,tot;
int X;
LL minx[N],ad[N],miad[N],val[N];
bool cmp(int x,int y){return a[x][D]<a[y][D];}
void pushup(int i)
{
mx[i][0]=max(max(mx[lc][0],mx[rc][0]),a[i][0]);
mx[i][1]=max(max(mx[lc][1],mx[rc][1]),a[i][1]);
mi[i][0]=min(min(mi[lc][0],mi[rc][0]),a[i][0]);
mi[i][1]=min(min(mi[lc][1],mi[rc][1]),a[i][1]);
}
void cal(int i,LL x,LL y)//x:miad y: ad
{
miad[i]=min(miad[i],ad[i]+x);
minx[i]=min(minx[i],val[i]+x);
ad[i]+=y;val[i]+=y;
}
void pushdown(int i)
{
if(miad[i]||ad[i]){
if(lc)cal(lc,miad[i],ad[i]);
if(rc)cal(rc,miad[i],ad[i]);
miad[i]=0;ad[i]=0;
}
}
void build(int &i,int l,int r,int d)
{
int mid=(l+r)>>1;D=d;
nth_element(tmp+l,tmp+mid,tmp+r+1,cmp);
i=tmp[mid];lc=rc=0;
if(l<mid)build(lc,l,mid-1,d^1);
if(r>mid)build(rc,mid+1,r,d^1);
pushup(i);if(lc)fa[lc]=i;if(rc)fa[rc]=i;
}
void insert(int i,LL k)
{
if(mi[i][0]>X||mx[i][1]<X)
return;
if(mx[i][0]<=X&&mi[i][1]>=X){
cal(i,min(0ll,k),k);
return;
}
pushdown(i);
if(a[i][0]<=X&&a[i][1]>=X){
minx[i]=min(minx[i],val[i]+min(0ll,k));
val[i]+=k;
}
if(lc)insert(lc,k);
if(rc)insert(rc,k);
}
void pdpath(int i){if(i!=rt)pdpath(fa[i]);pushdown(i);}
LL A[N],sum[N];
struct node{
int op,x,y,id;
}q[N];
int main()
{
mi[0][0]=mi[0][1]=0x3f3f3f3f;
mx[0][0]=mx[0][1]=0;
int n,m,i;
n=gi();m=gi();
for(i=1;i<=n;i++){
A[i]=gi();
sum[i]=sum[i-1]+A[i];
}
for(i=1;i<=m;i++){
q[i].op=gi();
q[i].x=gi();q[i].y=gi();
if(q[i].op==2){
tot++;
tmp[tot]=tot;
a[tot][0]=q[i].x;a[tot][1]=q[i].y;
val[tot]=minx[tot]=sum[q[i].y]-sum[q[i].x-1];
q[i].id=tot;
}
}
build(rt,1,tot,0);
for(i=1;i<=m;i++){
if(q[i].op==1){
X=q[i].x;
insert(rt,q[i].y-A[q[i].x]);
A[q[i].x]=q[i].y;
}
else{
pdpath(q[i].id);
printf("%lld\n",minx[q[i].id]);
}
}
}
20200525B
题解:
当点Ti为1时,点S_{p+i}要满足0<=a(p+i)+b<c,才能使答案不匹配
我们可以把括号打开,得:-ai<=ap+b<c-ai
发现我们的查询只与p有关,那么我们就可以把位置i看作对-ai到ai的一个区间覆盖1
这个可以动态开点线段树直接维护O(mlogn)
代码:(之前一直TLE,是因为线段树query里面没有写区间不相交的判断)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 100005
struct node{
int l,r,x;
}a[N<<8];
int rt,tot;
void insert(int &i,int l,int r,int x,int k)
{
if(!i)i=++tot;
a[i].x+=k;
if(l==r)return;
int mid=(l+r)>>1;
if(x<=mid)insert(a[i].l,l,mid,x,k);
else insert(a[i].r,mid+1,r,x,k);
}
int query(int i,int l,int r,int ql,int qr)
{
if(!i||ql>r||qr<l)return 0;
if(ql<=l&&r<=qr)return a[i].x;
int mid=(l+r)>>1;
return query(a[i].l,l,mid,ql,qr)+query(a[i].r,mid+1,r,ql,qr);
}
int T[N],L[N],R[N];
int main()
{
int n,k,b,c,m,Q,i,x,l,r,op;
n=gi();k=gi();b=gi();c=gi();m=gi();
for(i=0;i<m;i++){
scanf("%1d",&T[i]);
l=(1ll*c-1ll*b-1ll*k*i)%n;
r=(1ll*n-1ll*b-1ll*k*i)%n;
if(l<0)l+=n;if(r<0)r+=n;
L[i]=l;R[i]=r;
l=L[i];r=R[i];
if(T[i])swap(l,r);
if(l<=r){
insert(rt,0,n-1,l,1);
insert(rt,0,n-1,r,-1);
}
else{
insert(rt,0,n-1,0,1);
insert(rt,0,n-1,r,-1);
insert(rt,0,n-1,l,1);
}
}
Q=gi();
while(Q--){
op=gi();x=gi();
if(op==1){
x=1ll*k*x%n;
printf("%d\n",query(rt,0,n-1,0,x));
}
else{
l=L[x];r=R[x];
if(T[x])swap(l,r);
if(l<=r){
insert(rt,0,n-1,l,-1);
insert(rt,0,n-1,r,1);
}
else{
insert(rt,0,n-1,0,-1);
insert(rt,0,n-1,r,1);
insert(rt,0,n-1,l,-1);
}
T[x]^=1;
l=L[x];r=R[x];
if(T[x])swap(l,r);
if(l<=r){
insert(rt,0,n-1,l,1);
insert(rt,0,n-1,r,-1);
}
else{
insert(rt,0,n-1,0,1);
insert(rt,0,n-1,r,-1);
insert(rt,0,n-1,l,1);
}
}
}
}
20200525C
题解:
它的式子其实是
val[i][0]=i/n*val[i-1][0]+(n-i-1)/n*val[i+1][0]+1/n*(val[i+1][1]+1)
表示:选中一个权值为1的点翻转后的期望+选中一个除自身以外的0点翻转后的期望+翻转自己后的期望(贡献一步)
下面的反之亦然
解二元一次方程,我们可以维护两个未知数的系数与一个常数的三元组
有很多边界需要特盘
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 100005
int fir[N],to[2*N],nxt[2*N],cnt;
void adde(int a,int b)
{
to[++cnt]=b;nxt[cnt]=fir[a];fir[a]=cnt;
to[++cnt]=a;nxt[cnt]=fir[b];fir[b]=cnt;
}
const int mod=1000000007;
int n,a[N],dis[N],siz[N];
void dfs1(int u,int ff)
{
siz[u]=1;
for(int v,p=fir[u];p;p=nxt[p]){
if((v=to[p])!=ff){
dfs1(v,u);
dis[u]=(1ll*dis[u]+dis[v]+siz[v])%mod;
siz[u]+=siz[v];
}
}
}
void dfs2(int u,int ff)
{
for(int v,p=fir[u];p;p=nxt[p]){
if((v=to[p])!=ff){
dis[v]=((1ll*dis[v]+dis[u]-dis[v]-siz[v]+n-siz[v])%mod+mod)%mod;
dfs2(v,u);
}
}
}
int F[2],G[2],tong[2],inv[N];
struct node{
int a,b,c;
node(){a=b=c=0;}
node(int x,int y,int z){a=x;b=y;c=z;}
node operator + (const node &t)const{return node((a+t.a)%mod,(b+t.b)%mod,(c+t.c)%mod);}
node operator - (const node &t)const{return node((a+mod-t.a)%mod,(b+mod-t.b)%mod,(c+mod-t.c)%mod);}
node operator * (const int &t)const{return node(1ll*a*t%mod,1ll*b*t%mod,1ll*c*t%mod);}
int val(){return (1ll*a*F[0]+1ll*b*F[1]+1ll*c)%mod;}
}f[N][2],P,Q;
int ksm(int x,int y)
{
int ret=1;
while(y){
if(y&1)ret=1ll*ret*x%mod;
y>>=1;x=1ll*x*x%mod;
}
return ret;
}
int main()
{
int i,x,sum=0,ans=0;
scanf("%d",&n);
for(i=1;i<=n;i++){scanf("%1d",&a[i]);tong[a[i]]++;}
for(i=2;i<=n;i++){scanf("%d",&x);adde(i,x);}
if(n==2){
if(tong[0]==2||tong[1]==2)printf("0\n");
else printf("500000004\n");
return 0;
}
dfs1(1,0);dfs2(1,0);
f[1][0]=node(1,0,0);f[1][1]=node(0,1,0);
inv[0]=inv[1]=1;
for(i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(i=1;i<n-1;i++){
f[i+1][0]=(f[i][0]*n-f[i-1][0]*(i-1)-f[i-1][1]-node(0,0,i!=1))*inv[n-i];
f[i+1][1]=(f[i][1]*n-f[i-1][1]*i-f[i+1][0]-node(0,0,1))*inv[n-i-1];
}
P=(f[n-2][0]*(n-2)+f[n-2][1]+node(0,0,1))*inv[n]-f[n-1][0];
Q=f[n-2][1]*(1ll*(n-1)*inv[n]%mod)-f[n-1][1];
if(!P.b)swap(P,Q);
P=P-Q*(1ll*P.a*ksm(Q.a,mod-2)%mod);
F[1]=-1ll*P.c*ksm(P.b,mod-2)%mod;
F[0]=-1ll*(Q.c+1ll*Q.b*F[1])%mod*ksm(Q.a,mod-2)%mod;
G[0]=f[tong[0]][0].val();G[1]=f[tong[0]][1].val();
for(i=1;i<=n;i++){
ans=(1ll*ans+1ll*G[a[i]]*dis[i])%mod;
sum=(sum+dis[i])%mod;
}
printf("%d\n",(1ll*ans+1ll*sum*inv[n])%mod*inv[n]%mod);
}
20200526a
题解:n^2枚举*n^2简单DP
应该是可以优化成n^2预处理+n^2枚举的
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gi()
{
char c;int num=0,flg=1;
while((c=getchar())<'0'||c>'9')if(c=='-')flg=-1;
while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();}
return num*flg;
}
#define N 55
#define LL long long
LL f[N][N],sum1[N][N],sum2[N][N],mx1[N][N],mx2[N][N];
int a[N][N];
int main()
{
int T,n,K,i,j,k,l;
T=gi();
while(T--){
n=gi();K=gi();n--;
memset(f,0,sizeof(f));
memset(a,0,sizeof(a));
for(i=1;i<=n;i++)
for(j=0;j<=i;j++)
a[i-j][j]=gi();
if(K==2){
for(i=1;i<=n;i++){
for(j=1;j<=n;j++){
sum1[i][j]=sum1[i][j-1]+a[i][j];
sum2[i][j]=sum2[i-1][j]+a[i][j];
mx1[i][j]=max(mx1[i][j-1],sum1[i][j]);
mx2[i][j]=max(mx2[i-1][j],sum2[i][j]);
}
}
}
LL ans=0,s1=0,s2,ret;
for(i=0;i<=n;i++){
s1+=a[i][0];s2=0;
for(j=0;j<=n;j++){
s2+=a[0][j];ret=0;
if(K==2){
for(k=0;k<=i;k++){
for(l=0;l<=j;l++){
if(k==0&&l==0)continue;
f[k][l]=max(f[k][l-1]+(k==i?mx2[n][l]:mx2[k][l]),f[k-1][l]+(l==j?mx1[k][n]:mx1[k][l]));
ret=max(ret,f[k][l]);
}
}
}
ans=max(ans,s1+s2+ret);
}
}
printf("%lld\n",ans);
}
}
20200526b
题解:
先枚举把水全部聚集到一个地方[l,r]
显然把l以左的水全部聚集过来的代价至少为Σa[i]-mil[i](mil表示前缀最小值)
右边也是一样的
聚集过来了之后可以二分一下当前水面高度,把[l,r]中高出来的土整体向下铲
由于当右端点向右走一格,水面是不会上升的,所以我们可以利用这个单调性,先算出整数的水面高度(相当于估一个范围)
然后再精确计算实际的水面高度
代码:(之前前缀min值取错了,竟然水过了)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define N 5005
int T,n;
int a[N],w[N],cnt[1005],sum[N];
int sul[N],sur[N],mil[N],mir[N];
int main()
{
int i,j,sumw;
scanf("%d",&T);
while(T--){
sumw=0;
scanf("%d",&n);a[0]=a[n+1]=10000000;
for(i=1;i<=n;i++)scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
for(i=1;i<=n;i++)scanf("%d",&w[i]),sumw+=w[i];
memset(sul,0,sizeof(sul));memset(sur,0,sizeof(sur));
memset(mil,0,sizeof(mil));memset(mir,0,sizeof(mir));
int lp=0,rp=0;
for(i=1;i<=n;i++){
if(w[i]){
lp=i;mil[i]=a[i];
for(j=i+1;j<=n;j++)mil[j]=min(a[j],mil[j-1]);
for(j=i+1;j<=n;j++)sul[j]=sul[j-1]+a[j]-mil[j];
break;
}
}
for(i=n;i>=1;i--){
if(w[i]){
rp=i;mir[i]=a[i];
for(j=i-1;j>=1;j--)mir[j]=min(a[j],mir[j+1]);
for(j=i-1;j>=1;j--)sur[j]=sur[j+1]+a[j]-mir[j];
break;
}
}
int len,h,sumd;
double ans=1e9;
for(i=1;i<=n;i++){
memset(cnt,0,sizeof(cnt));
len=0;h=1000;sumd=0;
for(j=i;j<=n;j++){
if(a[j]<=h){
sumd+=h-a[j];
cnt[a[j]]++;
len++;//len means the number of underwater stones
while(sumd>sumw){// some stone will come out
len-=cnt[h];
sumd-=len;
h--;
}
}
double x=1.0*h+1.0*(sumw-sumd)/len;
double ret=(sum[j]-sum[i-1])-((j-i+1)*x-sumw);
int lh=a[i-1],rh=a[j+1];
if(lp<=i-1)lh=mil[i-1],ret+=sul[i-1];
if(j+1<=rp)rh=mir[j+1],ret+=sur[j+1];
if(x>min(lh,rh))ret+=(j-i+1)*(x-min(lh,rh));
ans=min(ans,ret);
}
}
printf("%.5f\n",ans);
}
}