POI2015题解

POI2015题解

吐槽一下为什么POI2015开始就成了破烂波兰文题目名了啊。。。

咕了一道3748没写打表题没什么意思,还剩\(BZOJ\)上的\(14\)道题。

[BZOJ3746][POI2015]Czarnoksiężnicy okrągłego stołu

这个题真的神仙。

\(p=0\),答案是\([n=1]\)

\(p=1\),答案是\([n=1]+[n=2,k=0]\)

\(p=2\),只有至多两种方案,即\(n\)左手边坐\(n-1,n-3...\),右手边坐\(n-2,n-4...\)或是反过来。分别判一下是否满足限制即可。

\(p=3\)

考虑增量构造,即按编号从大到小加入。当我们加入\(x\)时,它当前的左右两边一定要是\(x+1,x+2,x+3\)三者中的其二,否则将会不合法。可以发现,我们关心的只有\(x+1,x+2,x+3\)这三个数两两之间是否直接相邻以及它们的相对顺序(顺/逆时针排列),所以设状态\(f_{i,s,j}\)表示填了\(i...n\)\(i,i+1,i+2\)的排列方式是顺时针还是逆时针,\(i\)\(i+1\)\(i\)\(i+2\)\(i+1\)\(i+2\)之间分别有没有大于\(i+2\)的数(也就是是否直接相邻)。

转移的时候只有至多三种插入方式,分别讨论一下是否合法即可。注意最后\(i=1\)时要特判\(i,i+1,i+2\)之间是否合法。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e6+5;
const int mod = 1e9+7;
int n,k,p,f[N][2][8],a[N],b[N][7];
inline bool chk(int x,int y){
    if (abs(x-y)>p) return false;
    return !b[x][y-x+3];
}
inline bool judge(int x,int s,int j,int k){
    int s1=j>>2,s2=j>>1&1,s3=j&1,fg=1;
    if (!s)
        if (!k) fg&=(!s1)&(s2|chk(x+3,x+1))&(s3|chk(x+2,x+3));
        else if (k&1) fg&=(!s2)&(s3|chk(x+2,x+3))&chk(x+3,x);
        else fg&=(!s3)&(s2|chk(x+3,x+1))&chk(x,x+3);
    else
        if (!k) fg&=(!s1)&(s2|chk(x+1,x+3))&(s3|chk(x+3,x+2));
        else if (k&1) fg&=(!s2)&(s3|chk(x+3,x+2))&chk(x,x+3);
        else fg&=(!s3)&(s2|chk(x+1,x+3))&chk(x+3,x);
    if (x==1)
        if (!s)
            if (!k) fg&=chk(x+1,x)&chk(x,x+2);
            else if (k&1) fg&=chk(x,x+1)&(s1|chk(x+1,x+2));
            else fg&=chk(x+2,x)&(s1|chk(x+1,x+2));
        else
            if (!k) fg&=chk(x,x+1)&chk(x+2,x);
            else if (k&1) fg&=chk(x+1,x)&(s1|chk(x+2,x+1));
            else fg&=chk(x,x+2)&(s1|chk(x+2,x+1));
    return fg;
}
inline void upt(int &x,int y){x+=y;x>=mod?x-=mod:x;}
int main(){
    n=gi();k=gi();p=gi();
    for (int i=1;i<=k;++i){
        int x=gi(),y=gi();
        if (abs(x-y)>3) continue;
        b[x][y-x+3]=1;
    }
    if (p==0) return puts(n==1?"1":"0"),0;
    if (p==1) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
    if (p==2){
        if (n<=2) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
        int fg=1,ans=0;a[1]=n;
        for (int i=2,j=n-1;j>0;++i,j-=2) a[i]=j;
        for (int i=n,j=n-2;j>0;--i,j-=2) a[i]=j;
        for (int i=1;i<n;++i) fg&=chk(a[i],a[i+1]);fg&=chk(a[n],a[1]);
        ans+=fg;fg=1;
        for (int i=2,j=n-2;j>0;++i,j-=2) a[i]=j;
        for (int i=n,j=n-1;j>0;--i,j-=2) a[i]=j;
        for (int i=1;i<n;++i) fg&=chk(a[i],a[i+1]);fg&=chk(a[n],a[1]);
        ans+=fg;printf("%d\n",ans);
    }
    if (p==3){
        if (n<=2) return puts(n==1||(n==2&&k==0)?"1":"0"),0;
        f[n-2][0][0]=f[n-2][1][0]=1;
        for (int i=n-2;i>1;--i)
            for (int s=0;s<2;++s)
                for (int j=0;j<8;++j)
                    if (f[i][s][j]){
                        if (judge(i-1,s,j,0)) upt(f[i-1][s^1][1],f[i][s][j]);
                        if (judge(i-1,s,j,1)) upt(f[i-1][s][2|j>>2],f[i][s][j]);
                        if (judge(i-1,s,j,2)) upt(f[i-1][s][4|j>>2],f[i][s][j]);
                    }
        int ans=0;
        for (int s=0;s<2;++s)
            for (int j=0;j<8;++j)
                upt(ans,f[1][s][j]);
        printf("%d\n",ans);
    }
    return 0;
}

[BZOJ3747][POI2015]Kinoman

从左到右枚举右端点,维护左端点的答案。

需要支持的操作是区间加和区间取最大值。用线段树维护即可。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e6+5;
int n,m,a[N],lst[N],pos[N],b[N];long long mx[N<<2],tag[N<<2],ans;
void modify(int x,int l,int r,int ql,int qr,int v){
    if (l>=ql&&r<=qr) {mx[x]+=v;tag[x]+=v;return;}
    int mid=l+r>>1;
    if (ql<=mid) modify(x<<1,l,mid,ql,qr,v);
    if (qr>mid) modify(x<<1|1,mid+1,r,ql,qr,v);
    mx[x]=max(mx[x<<1],mx[x<<1|1])+tag[x];
}
int main(){
    n=gi();m=gi();
    for (int i=1;i<=n;++i) lst[i]=pos[a[i]=gi()],pos[a[i]]=i;
    for (int i=1;i<=m;++i) b[i]=gi();
    for (int i=1;i<=n;++i){
        modify(1,1,n,1,i,b[a[i]]);
        if (lst[i]){
            if (lst[lst[i]]) modify(1,1,n,1,lst[lst[i]],b[a[i]]);
            modify(1,1,n,1,lst[i],-b[a[i]]<<1);
        }
        ans=max(ans,mx[1]);
    }
    printf("%lld\n",ans);return 0;
}

[BZOJ3749][POI2015]Łasuchy

对每盘食物设一个状态\(S=\{0,1,2,3\}\)表示是否每相邻的两个人吃,然后就可以大力\(dp\)了。

枚举起点,\(dp\)一圈回来判一下是否合法。记录前驱输出方案即可。

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e6+5;
int n,a[N],S,f[N][4],g[N][4],b[N];
bool dp(){
    memset(f,0,sizeof(f));f[0][S]=1;
    for (int i=1;i<=n;++i){
        if (f[i-1][1]&&a[i-1]>=a[i]) f[i][0]=1,g[i][0]=1;
        else if (f[i-1][3]&&a[i-1]>=2*a[i]) f[i][0]=1,g[i][0]=3;

        if (f[i-1][1]&&2*a[i-1]>=a[i]) f[i][1]=1,g[i][1]=1;
        else if (f[i-1][3]&&a[i-1]>=a[i]) f[i][1]=1,g[i][1]=3;

        if (f[i-1][0]&&a[i]>=a[i-1]) f[i][2]=1,g[i][2]=0;
        else if (f[i-1][2]&&2*a[i]>=a[i-1]) f[i][2]=1,g[i][2]=2;

        if (f[i-1][0]&&a[i]>=2*a[i-1]) f[i][3]=1,g[i][3]=0;
        else if (f[i-1][2]&&a[i]>=a[i-1]) f[i][3]=1,g[i][3]=2;
    }
    return f[n][S];
}
int main(){
    n=gi();
    for (int i=1;i<=n;++i) a[i]=gi();a[0]=a[n];
    for (S=0;S<4;++S) if (dp()) break;
    if (S==4) return puts("NIE"),0;
    for (int i=n;i;--i){
        if (S&1) b[i]=i;
        if (S&2) b[i-1?i-1:n]=i;
        S=g[i][S];
    }
    for (int i=1;i<=n;++i) printf("%d ",b[i]);
    puts("");return 0;
}

[BZOJ3750][POI2015]Pieczęć

确定最左上的那个点,直接染色就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1005;
int n,m,a,b,t,f,g[N][N],dx[N*N],dy[N*N];char s[N];
bool cover(int p,int q){
    for (int i=1;i<=t;++i){
        int x=p+dx[i],y=q+dy[i];
        if (x<1||x>n||y<1||y>m||!g[x][y]) return false;
        g[x][y]=0;
    }
    return true;
}
int main(){
    int Case=gi();while (Case--){
        n=gi();m=gi();a=gi();b=gi();t=0;f=1;
        for (int i=1;i<=n;++i){
            scanf("%s",s+1);
            for (int j=1;j<=m;++j) g[i][j]=s[j]=='x';
        }
        for (int i=1;i<=a;++i){
            scanf("%s",s+1);
            for (int j=1;j<=b;++j)
                if (s[j]=='x') dx[++t]=i,dy[t]=j;
        }
        for (int i=2;i<=t;++i) dx[i]-=dx[1],dy[i]-=dy[1];
        dx[1]=dy[1]=0;
        for (int i=1;i<=n;++i)
            for (int j=1;j<=m;++j)
                if (g[i][j]&&!cover(i,j)) {f=0;break;}
        puts(f?"TAK":"NIE");
    }
    return 0;
}

[BZOJ4377][POI2015]Kurs szybkiego czytania

设匹配位置的第一个下标位置为\(x\)

那么串\(s\)相当于给出限制:\(a(x+i)+b\mod n\ge p\)或是\(a(x+i)+b\mod n< p\)

于是就可以确定\(ax\)这个量在\([0,n)\)内的取值范围,或者说是不能取到的范围

所以拿个扫描线维护一下这个东西就好了。

注意最后\(m-1\)个位置是无论如何不能匹配的,所以还要特殊处理一下。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e6+5;
int n,a,b,p,m,cnt,ans;char s[N];
struct node{
    int x,y;
    bool operator < (const node &b) const
        {return x<b.x;}
}t[N<<3];
void cover(int l,int r){
    t[++cnt]=(node){l,1};t[++cnt]=(node){r+1,-1};
}
int main(){
    n=gi();a=gi();b=gi();p=gi();m=gi();scanf("%s",s+1);
    for (int i=1,x=b;i<=m;++i,(x+=a)%=n)
        if (s[i]-'0')
            if (x<p) cover(n-x,n-1),cover(0,p-x-1);
            else cover(n-x,p+n-x-1);
        else
            if (x<=p) cover(p-x,n-x-1);
            else cover(0,n-x-1),cover(p+n-x,n-1);
    for (int i=n-m+1;i<n;++i) cover(1ll*a*i%n,1ll*a*i%n);
    sort(t+1,t+cnt+1);t[++cnt]=(node){n,0};
    for (int i=1,s=0;i<=cnt;++i){
        if (t[i].x!=t[i-1].x)
            if (!s) ans+=t[i].x-t[i-1].x;
        s+=t[i].y;
    }
    printf("%d\n",ans);
    return 0;
}

[BZOJ4378][POI2015]Logistyka

对于每次询问,小于等于\(s\)的数可以被减成零,而大于\(s\)的数至多减\(s\),所以就只需要比较\(c\times s\)与小于等于\(s\)的数之和\(+\)大于\(s\)的数的个数\(\times s\)这两个数大小就行了。用两个\(BIT\)维护。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 1e6+5;
int n,m,op[N],a[N],b[N],o[N],len,val[N],c1[N];ll c2[N];

void mdf1(int k,int v){while(k<=len)c1[k]+=v,k+=k&-k;}
int qry1(int k){int s=0;while(k)s+=c1[k],k^=k&-k;return s;}
void mdf2(int k,int v){while(k<=len)c2[k]+=v,k+=k&-k;}
ll qry2(int k){ll s=0;while(k)s+=c2[k],k^=k&-k;return s;}

void add(int x){mdf1(x,1),mdf2(x,o[x]);}
void del(int x){mdf1(x,-1),mdf2(x,-o[x]);}

int main(){
    n=gi();m=gi();
    for (int i=1;i<=m;++i) op[i]=getchar()=='U',a[i]=gi(),o[++len]=b[i]=gi();
    o[++len]=0;sort(o+1,o+len+1);len=unique(o+1,o+len+1)-o-1;
    for (int i=1;i<=m;++i) b[i]=lower_bound(o+1,o+len+1,b[i])-o;
    mdf1(1,n);
    for (int i=1;i<=n;++i) val[i]=1;
    for (int i=1;i<=m;++i)
        if (op[i]) del(val[a[i]]),val[a[i]]=b[i],add(val[a[i]]);
        else puts(1ll*(qry1(len)-qry1(b[i]))*o[b[i]]+qry2(b[i])>=1ll*a[i]*o[b[i]]?"TAK":"NIE");
    return 0;
}

[BZOJ4379][POI2015]Modernizacja autostrady

毒瘤warning!

换根\(dp\)求出切断每条边之后两棵树内的直径。需要维护:子树内从\(u\)出发的前三长链,子树内最大次大直径(不算当前点),子树最大直径,子树外最长链与最大直径。

设两棵树的直径分别是\(A,B\),那么连接两直径中点可得最小直径\(\max\{A,B,\lceil\frac A2\rceil+\lceil\frac B2\rceil+1\}\),连接两直径端点可得最大直径\(A+B+1\)

然后随便构造一下方案就好了。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 5e5+5;
int n,to[N<<1],nxt[N<<1],head[N],cnt=1,d[N][3],w[N][2],g[N],o[N],h[N],ans1=1e9,edg1,ans2,edg2,ban[N<<1],pre[N],dep[N],S,tmp[N],len;
void dfs1(int u,int f){
    for (int e=head[u],v;e;e=nxt[e])
        if ((v=to[e])!=f){
            dfs1(v,u);
            int t=d[v][0]+1;
            if (t>d[u][0]) d[u][2]=d[u][1],d[u][1]=d[u][0],d[u][0]=t;
            else if (t>d[u][1]) d[u][2]=d[u][1],d[u][1]=t;
            else if (t>d[u][2]) d[u][2]=t;
            t=g[v];g[u]=max(g[u],g[v]);
            if (t>w[u][0]) w[u][1]=w[u][0],w[u][0]=t;
            else if (t>w[u][1]) w[u][1]=t;
        }
    g[u]=max(g[u],d[u][0]+d[u][1]);
}
void dfs2(int u,int f){
    for (int e=head[u],v;e;e=nxt[e])
        if ((v=to[e])!=f){
            o[v]=max(o[u],d[v][0]+1==d[u][0]?d[u][1]:d[u][0])+1;
            h[v]=max(h[u],g[v]==w[u][0]?w[u][1]:w[u][0]);
            int a,b;
            if (d[v][0]+1==d[u][0]) a=d[u][1],b=d[u][2];
            else a=d[u][0],b=d[v][0]+1==d[u][1]?d[u][2]:d[u][1];
            h[v]=max(h[v],a+b);h[v]=max(h[v],a+o[u]);
            int t=max(max(h[v],g[v]),(h[v]+1)/2+(g[v]+1)/2+1);
            if (t<ans1) ans1=t,edg1=e;
            t=h[v]+g[v]+1;
            if (t>ans2) ans2=t,edg2=e;
            dfs2(v,u);
        }
}
void DFS(int u,int f){
    pre[u]=f;dep[u]=dep[f]+1;S=dep[u]>dep[S]?u:S;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f&&!ban[e]) DFS(to[e],u);
}
int main(){
    n=gi();
    for (int i=1;i<n;++i){
        int u=gi(),v=gi();
        to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
        to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    }
    dfs1(1,0);dfs2(1,0);
    printf("%d %d %d ",ans1,to[edg1],to[edg1^1]);ban[edg1]=ban[edg1^1]=1;
    DFS(S=to[edg1],0);DFS(S,len=0);
    for (int i=S;i;i=pre[i]) tmp[++len]=i;printf("%d ",tmp[len+1>>1]);
    DFS(S=to[edg1^1],0);DFS(S,len=0);
    for (int i=S;i;i=pre[i]) tmp[++len]=i;printf("%d\n",tmp[len+1>>1]);
    printf("%d %d %d ",ans2,to[edg2],to[edg2^1]);ban[edg1]=ban[edg1^1]=0;ban[edg2]=ban[edg2^1]=1;
    DFS(S=to[edg2],0);printf("%d ",S);
    DFS(S=to[edg2^1],0);printf("%d\n",S);
    return 0;
}

[BZOJ4380][POI2015]Myjnie

\(f_{i,j,k}\)表示区间\([i,j]\)最小费用为\(k\)的最大收益。为了方便转移记成后缀最大值的形式。

暴力枚举区间内最小值的位置以及这个最小值转移,复杂度\(O(n^3m)\)

输出方案要多记录一些东西,比如\(g_{i,j,k}\)记录后缀最大值的真实的\(k\)的位置,\(h_{i,j,k}\)记录转移点(最小值的位置)。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 55;
const int M = 4005;
int n,m,a[M],b[M],c[M],o[M],tot[N][M],f[N][N][M],g[N][N][M],h[N][N][M],ans[N];
void add(int l,int r){
    for (int i=l;i<=r;++i)
        for (int j=1;j<=m;++j)
            tot[i][j]=0;
    for (int i=1;i<=m;++i)
        if (a[i]>=l&&b[i]<=r)
            for (int j=a[i];j<=b[i];++j)
                ++tot[j][c[i]];
    for (int i=l;i<=r;++i)
        for (int j=m-1;j;--j)
            tot[i][j]+=tot[i][j+1];
}
void dfs(int l,int r,int k){
    if (l>r) return;
    k=g[l][r][k];int x=h[l][r][k];
    ans[x]=o[k];dfs(l,x-1,k);dfs(x+1,r,k);
}
int main(){
    n=gi();m=gi();
    for (int i=1;i<=m;++i) a[i]=gi(),b[i]=gi(),c[i]=o[i]=gi();
    sort(o+1,o+m+1);
    for (int i=1;i<=m;++i) c[i]=lower_bound(o+1,o+m+1,c[i])-o;
    for (int i=n;i;--i)
        for (int j=i;j<=n;++j){
            add(i,j);
            for (int k=m;k;--k){
                int res=-1;
                for (int x=i;x<=j;++x){
                    int tmp=f[i][x-1][k]+f[x+1][j][k]+tot[x][k]*o[k];
                    if (tmp>res) res=tmp,h[i][j][k]=x;
                }
                if (res>=f[i][j][k+1]) f[i][j][k]=res,g[i][j][k]=k;
                else f[i][j][k]=f[i][j][k+1],g[i][j][k]=g[i][j][k+1];
            }
        }
    printf("%d\n",f[1][n][1]);dfs(1,n,1);
    for (int i=1;i<=n;++i) printf("%d ",ans[i]);puts("");
    return 0;
}

[BZOJ4381][POI2015]Odwiedziny

POI居然会出\(5w\)的数据范围那就一定是根号算法了吧。

按步伐大小分块:小于等于\(\sqrt n\)的可以\(O(n\sqrt n)\)预处理每个点以\(i\)的步伐向上跳到根的权值和,大于\(\sqrt n\)的暴力跳即可。处理起来或许有点小细节。

这样复杂度是\(O(n\sqrt n)\)的。树剖跳\(k\)级祖先的复杂度看上去像是\(O(\log n)\)的,但对于一组询问重链的变换次数只有\(O(\log n)\),所以单次的复杂度应该是\(O(\sqrt n+\log n)\)而非两者乘起来。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 50005;
const int M = 100;
int n,m,a[N],to[N<<1],nxt[N<<1],head[N],cnt,fa[N],dep[N],sz[N],top[N],dfn[N],id[N],tim,s[N],sum[N][M],b[N],c[N];
void dfs1(int u,int f){
    fa[u]=f;dep[u]=dep[f]+1;sz[u]=1;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f) dfs1(to[e],u),sz[u]+=sz[to[e]];
}
void dfs2(int u,int f){
    top[u]=f;id[dfn[u]=++tim]=u;int son=0;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=fa[u]&&sz[to[e]]>sz[son]) son=to[e];
    if (son) dfs2(son,f);else return;
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=fa[u]&&to[e]!=son) dfs2(to[e],to[e]);
}
void dfs(int u,int f){
    s[dep[u]]=u;
    for (int i=1;i<M;++i){
        sum[u][i]=a[u];
        if (dep[u]>i) sum[u][i]+=sum[s[dep[u]-i]][i];
    }
    for (int e=head[u];e;e=nxt[e])
        if (to[e]!=f) dfs(to[e],u);
}
int father(int u,int k){
    if (k>=dep[u]) return 0;
    while (dep[u]-dep[top[u]]<k) k-=dep[u]-dep[top[u]]+1,u=fa[top[u]];
    return id[dfn[u]-k];
}
int lca(int u,int v){
    while (top[u]^top[v])
        if (dep[top[u]]>dep[top[v]]) u=fa[top[u]];
        else v=fa[top[v]];
    return dep[u]<dep[v]?u:v;
}
int jump(int x,int d,int k){
    if (k<M) return sum[x][k]-sum[father(x,(d/k+1)*k)][k];
    int res=0;
    while (d>=0) res+=a[x],d-=k,x=father(x,k);
    return res;
}
int work(int x,int y,int k){
    int z=lca(x,y),res=jump(x,dep[x]-dep[z],k);
    if ((dep[x]+dep[y]-2*dep[z])%k) res+=a[y];
    else y=father(y,(dep[x]+dep[y]-2*dep[z])%k);
    res+=jump(y,dep[y]-dep[z],k);
    if ((dep[x]-dep[z])%k==0) res-=a[z];
    return res;
}
int main(){
    n=gi();
    for (int i=1;i<=n;++i) a[i]=gi();
    for (int i=1;i<n;++i){
        int u=gi(),v=gi();
        to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
        to[++cnt]=u;nxt[cnt]=head[v];head[v]=cnt;
    }
    dfs1(1,0);dfs2(1,1);dfs(1,0);
    for (int i=1;i<=n;++i) b[i]=gi();
    for (int i=1;i<n;++i) c[i]=gi();
    for (int i=1;i<n;++i) printf("%d\n",work(b[i],b[i+1],c[i]));
    return 0;
}

[BZOJ4382][POI2015]Podział naszyjnika

对于两个相邻的同色块,我们可以在前一个上面打一个\(+1\)标记,在后一个上面打一个\(-1\)标记,然后就变成了选两个前缀他们的前缀和相等,相当于是这样个同色块之间不能割裂开来。

但是有若干种颜色每种颜色有若干块,标记可能会冲突,怎么办呢?我们给每对相邻同色块随机打上不同的标记就行啦。

这样第一问就是每种相同前缀数量\(sz\)\(\sum\binom{sz}{2}\),第二问可以把这些前缀拿出来做一个单调队列优化转移的\(dp\)状物。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 1e6+5;
int n,k,lst[N],id[N],ans2=N;ll p=1,sum[N],ans1;
bool cmp(int i,int j){return sum[i]==sum[j]?i<j:sum[i]<sum[j];}
int cal(int i,int j){
    int s=abs(n-(j-i<<1));
    ans2=min(ans2,s);return s;
}
int main(){
    n=gi();k=gi();
    for (int i=1;i<=n;++i){
        int a=gi();
        if (lst[a]) sum[lst[a]]+=p,sum[i]-=p,p*=20020415;
        lst[a]=i;
    }
    for (int i=1;i<=n;++i) sum[i]+=sum[i-1],id[i]=i;
    sort(id+1,id+n+1,cmp);
    for (int i=1,j=1;i<=n;i=j=j+1){
        while (j<n&&sum[id[j+1]]==sum[id[i]]) ++j;
        ans1+=1ll*(j-i)*(j-i+1)>>1;
        for (int k=i,l=i;k<=j;++k)
            while (l<k&&cal(id[l+1],id[k])<cal(id[l],id[k])) ++l;
    }
    printf("%lld %d\n",ans1,ans2);
    return 0;
}

[BZOJ4383][POI2015]Pustynia

暴力建图就是个差分约束状物。拿线段树优化连边可以做到边数\(O(\sum k\log n)\)

注意判一下无解,包括可能某个数超过了\(10^9\)也算是无解。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 4e5+5;
int n,s,m,tot,pos[N],root,ls[N],rs[N],tmp[N],to[N*20],nxt[N*20],ww[N*20],head[N],cnt,in[N],f[N],t[N];
queue<int>Q;
void link(int u,int v,int w){
    to[++cnt]=v;nxt[cnt]=head[u];ww[cnt]=w;head[u]=cnt;++in[v];
}
void build(int &x,int l,int r){
    x=++tot;if (l==r) {pos[l]=x;return;}
    int mid=l+r>>1;
    build(ls[x],l,mid);build(rs[x],mid+1,r);
    link(ls[x],x,0);link(rs[x],x,0);
}
void modify(int x,int l,int r,int ql,int qr){
    if (l>=ql&&r<=qr) {link(x,tot,0);return;}
    int mid=l+r>>1;
    if (ql<=mid) modify(ls[x],l,mid,ql,qr);
    if (qr>mid) modify(rs[x],mid+1,r,ql,qr);
}
int main(){
    n=gi();s=gi();m=gi();
    build(root,1,n);memset(t,63,sizeof(t));
    while (s--){
        int p=gi();f[pos[p]]=t[pos[p]]=gi();
    }
    while (m--){
        int l=gi(),r=gi(),k=gi();tmp[0]=l-1,tmp[k+1]=r+1;++tot;
        for (int i=1;i<=k;++i) tmp[i]=gi(),link(tot,pos[tmp[i]],1);
        for (int i=0;i<=k;++i) if (tmp[i+1]-tmp[i]>1) modify(root,1,n,tmp[i]+1,tmp[i+1]-1);
    }
    for (int i=1;i<=tot;++i) if (!in[i]) f[i]=max(f[i],1),Q.push(i);
    while (!Q.empty()){
        int u=Q.front();Q.pop();
        for (int e=head[u];e;e=nxt[e]){
            f[to[e]]=max(f[to[e]],f[u]+ww[e]);
            if (f[to[e]]>t[to[e]]) return puts("NIE"),0;
            if (!--in[to[e]]) Q.push(to[e]);
        }
    }
    for (int i=1;i<=n;++i) if (!f[pos[i]]||f[pos[i]]>1000000000) return puts("NIE"),0;
    puts("TAK");
    for (int i=1;i<=n;++i) printf("%d ",f[pos[i]]);
    puts("");return 0;
}

[BZOJ4384][POI2015]Trzy wieże

首先只有一种字符的可以预先\(O(n)\)判掉。然后就是三种字符的出现次数均不相同。

对每个位置分别记下三种字符的前缀数量\(cnt_{i,0/1/2}\)

问题要求的是:

\[cnt_{i,0}-cnt_{j,0}\neq cnt_{i,1}-cnt_{j,1}\\cnt_{i,0}-cnt_{j,0}\neq cnt_{i,2}-cnt_{j,2}\\cnt_{i,1}-cnt_{j,1}\neq cnt_{i,2}-cnt_{j,2}\]

也就是

\[cnt_{i,0}-cnt_{i,1}\neq cnt_{j,0}-cnt_{j,1}\\cnt_{i,0}-cnt_{i,2}\neq cnt_{j,0}-cnt_{j,2}\\cnt_{i,1}-cnt_{i,2}\neq cnt_{j,1}-cnt_{j,2}\]

\(a_i=cnt_{i,0}-cnt_{i,1},b_i=cnt_{i,0}-cnt_{i,2},c_i=cnt_{i,1}-cnt_{i,2}\),相当于是要求\(\max\{i-j|a_i\neq a_j,b_i\neq b_j,c_i\neq c_j\}\)

\(a_i\)排序,以\(b_i\)为下标插入树状数组,树状数组每个点上维护下标的最大最小值。因为有\(c_i\)的限制,所以要维护\(c_i\)值不同的最大次大值与最小次小值。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
const int N = 1e6+5;
int n,cnt[3],a[N],b[N],c[N],o[N],len,id[N],ans;char s[N];
bool cmp(int i,int j){return a[i]<a[j];}
struct node{
    int mx1,mx2,mn1,mn2;
    node(){mx1=mx2=0;mn1=mn2=N-1;}
    void upt(int x){
        if (x>mx1){
            if (c[x]!=c[mx1]) mx2=mx1;
            mx1=x;
        }
        else if (x>mx2&&c[x]!=c[mx1]) mx2=x;
        if (x<mn1){
            if (c[x]!=c[mn1]) mn2=mn1;
            mn1=x;
        }
        else if (x<mn2&&c[x]!=c[mn1]) mn2=x;
    }
};
struct Binary_Index_Tree{
    node t[N];
    void mdf(int k,int v){while(k<=len)t[k].upt(v),k+=k&-k;}
    int qry_mx(int k,int ban){
        int s=0;
        while (k) s=max(s,c[t[k].mx1]==ban?t[k].mx2:t[k].mx1),k^=k&-k;
        return s;
    }
    int qry_mn(int k,int ban){
        int s=n+1;
        while (k) s=min(s,c[t[k].mn1]==ban?t[k].mn2:t[k].mn1),k^=k&-k;
        return s;
    }
}T[2];
int main(){
    n=gi()+1;scanf("%s",s+2);
    for (int i=1;i<=n;++i){
        if (i>1) ++cnt[s[i]=='S'?2:s[i]-'B'];
        a[i]=cnt[1]-cnt[0];b[i]=cnt[2]-cnt[0];c[i]=cnt[2]-cnt[1];
    }
    for (int i=1;i<=n;++i) o[i]=b[i],id[i]=i;
    sort(o+1,o+n+1);len=unique(o+1,o+n+1)-o-1;
    for (int i=1;i<=n;++i) b[i]=lower_bound(o+1,o+len+1,b[i])-o;
    sort(id+1,id+n+1,cmp);
    for (int i=1,j=1;i<=n;i=j=j+1){
        while (j<n&&a[id[j+1]]==a[id[i]]) ++j;
        for (int k=i;k<=j;++k){
            int x=id[k];
            ans=max(ans,T[0].qry_mx(b[x]-1,c[x])-x);
            ans=max(ans,x-T[0].qry_mn(b[x]-1,c[x]));
            ans=max(ans,T[1].qry_mx(len-b[x],c[x])-x);
            ans=max(ans,x-T[1].qry_mn(len-b[x],c[x]));
        }
        for (int k=i;k<=j;++k){
            int x=id[k];
            T[0].mdf(b[x],x);T[1].mdf(len-b[x]+1,x);
        }
    }
    for (int i=2,j;i<=n;++i){
        if (s[i]!=s[i-1]) j=0;
        ++j;ans=max(ans,j);
    }
    printf("%d\n",ans);return 0;
}

[BZOJ4385][POI2015]Wilcze doły

一定会选长度为\(d\)的区间。\(\mbox{Two-points}\)+单调队列维护即可。

#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 2e6+5;
int n,d,q[N],hd=1,tl,ans;ll p,sum[N],val[N];
int main(){
    n=gi();scanf("%lld",&p);d=gi();
    for (int i=1;i<=n;++i) sum[i]=sum[i-1]+gi(),val[i]=sum[i]-sum[max(0,i-d)];
    for (int i=1,j=1;i<=n;++i){
        while (hd<=tl&&val[q[tl]]<=val[i]) --tl;
        q[++tl]=i;
        while (sum[i]-sum[j-1]-val[q[hd]]>p){
            ++j;while (q[hd]-d+1<j) ++hd;
        }
        ans=max(ans,i-j+1);
    }
    printf("%d\n",ans);
    return 0;
}

[BZOJ4386][POI2015]Wycieczki

裸的倍增矩乘。新建一个\(0\)号点表示路径的终点即可。

\(\mbox{long long}\)这点很烦,代码中判断如果乘出来的数超过了\(K\)就直接赋成\(-1\)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int gi(){
    int x=0,w=1;char ch=getchar();
    while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
    if (ch=='-') w=0,ch=getchar();
    while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
    return w?x:-x;
}
#define ll long long
const int N = 125;
int n,m,sz;ll K,ans;
struct matrix{
    ll a[N][N];
    matrix(){memset(a,0,sizeof(a));}
    ll *operator [](int x){return a[x];}
    matrix operator * (matrix b){
        matrix c;
        for (int i=0;i<=sz;++i)
            for (int j=0;j<=sz;++j)
                for (int k=0;k<=sz;++k)
                    if (a[i][k]&&b[k][j]){
                        if (a[i][k]<0||b[k][j]<0) {c[i][j]=-1;break;}
                        if (a[i][k]>K/b[k][j]) {c[i][j]=-1;break;}
                        c[i][j]+=a[i][k]*b[k][j];
                        if (c[i][j]<0) {c[i][j]=-1;break;}
                    }
        return c;
    }
    bool check(){
        ll res=0;
        for (int i=1;i<=n;++i){
            if (a[i][0]<0) return false;
            res+=a[i][0];
            if (res>=K||res<0) return false;
        }
        return true;
    }
}T[63],Now,Tmp;
int main(){
    n=gi();m=gi();sz=3*n;scanf("%lld",&K);K+=n;T[0][0][0]=1;
    for (int i=1;i<=n;++i) Now[i][i]=T[0][i][0]=T[0][i][i+n]=T[0][i+n][i+n+n]=1;
    for (int i=1,u,v,w;i<=m;++i) u=gi(),v=gi(),w=gi()-1,++T[0][u+w*n][v];
    for (int i=1;i<63;++i) T[i]=T[i-1]*T[i-1];
    for (int i=62;~i;--i){
        Tmp=Now*T[i];
        if (Tmp.check()) ans|=1ll<<i,Now=Tmp;
    }
    if ((Now*T[0]).check()) puts("-1");
    else printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/zhoushuyu/p/9775146.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值