9.16六省联考day2

T1 摧毁“树状图”(treediagram)

Sol
树形DP,大半内容参考Here
话说谁想得到这么多阴间状态啊
反正直接算正解,所以特殊性质直接空跑就完事力!
首先确定答案的几种方式:
对于一个以\(p\)为根的子树中

  • 一条以\(p\)为一个端点的链,定义为\(f(p,0)\)
  • 一条不经过\(p\)的链,定义为\(f(p,1)\)
  • 一条经过但不以\(p\)为一个端点的链,定义为\(f(p,2)\)
  • 一条以\(p\)为一个端点的链加上一条不以\(p\)为端点的链,定义为\(f(p,3)\)
    那么通过可以得出如下方程:
    对于一个以\(p\)为根的树和它的一个以\(q\)为根的子树:
    可以选取答案\(f(p,3)+f(q,0)-(p==1)\),表示一条从\(q\)过来的链加上\(p\)剩余的地方的一条相交的链答案和。\(f(p,0)+f(q,3)\)同理。这里要注意如果\(p=1\)则答案要减一,因为少了上面的一个连通块。
    可以选取答案\(f(p,1)+f(q,2)\),表示一条\(q\)里面的链加上一条不过\(p\)的链,由于\(p\)没被选上,所以答案不需要减一。
    可以选取答案\(f(p,1)+f(q,1)-1\),表示两条分别在\(p,q\)里面的链,由于没有把\(q\)分开,所以答案固定减一。
    可以选取答案\(f(p,2)+f(q,1)-(p==1)\),表示选取一条\(p\)端点的链和一条\(q\)里面的链,特殊判断同上。
    可以选取答案\(f(p,2)+f(q,2)-(p==1)\),表示选取一条\(p\)端点的链和一条\(q\)端点的链,特殊判断同上。
    接下来是维护\(f\)。(后面的等号自动视为取\(max\))
    首先是\(f(p,0)=f(q,0)+deg(q)-1\),表示从\(q\)连上来的链,删了一个点所以要减一。
    其次是\(f(p,1)=f(q,1)\)以及\(f(p,1)=f(q,2)+1\),表示直接继承或者过\(q\)的链。
    然后是\(f(p,2)=f(p,0)+f(q,0)-1\),表示从两边分别选择一条路径连接起来。
    最后是重头戏:\(f(p,3)\)
    \(f(p,3)=f(p,0)+f(q,2)-1\)
    \(f(p,3)=f(p,0)+f(q,1)-1\)这两个表示\(q\)里面选一个加上过\(p\)的一条链。
    \(f(p,3)=f(p,2)+f(q,0)-1\)这个表示选一个过\(q,p\)的链加上\(p\)里面的。
    \(f(p,3)=f(q,3)+deg(p)-1\)这个表示从\(q\)的答案中继承上来,把连接到\(q\)的链延伸。
    \(f(p,3)=f(q,0)+deg(p)+ret-2\)其中\(ret\)表示此前便历到的最大\(f(q,1)\)\(f(q,2)\)。这个表示从\(q\)连上来一条以及从其他子树里面找一条路径相加。
    然后按照方程写一个树形DP板子即可。注意是同时维护,所以要用到的后维护。
    贺都贺半天
    Code
#include<bits/stdc++.h>
#define max maxx
using namespace std;
const int maxn=100010;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=0;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return f?x:-x;
}
int T,n,x;
struct edge
{
    int to,next;
}e[maxn<<1];
int h[maxn],ei;
inline void add(int x,int y)
{
    e[++ei]=(edge){y,h[x]};
    h[x]=ei;return;
}
int deg[maxn],f[maxn][4],ans;
inline int maxx(int x,int y){return x>y?x:y;};
inline void dfs(int x,int fa)
{
    f[x][0]=f[x][2]=f[x][3]=deg[x];
    f[x][1]=1;int ret=0;
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(to==fa)continue;
        dfs(to,x);
        ans=max(ans,f[x][3]+f[to][0]-(x==1));
        ans=max(ans,f[x][0]+f[to][3]-(x==1));
        ans=max(ans,f[x][1]+f[to][2]);
        ans=max(ans,f[x][1]+f[to][1]-1);
        ans=max(ans,f[x][2]+f[to][1]-(x==1));
        ans=max(ans,f[x][2]+f[to][2]-(x==1));
        f[x][1]=max(f[x][1],f[to][1]);
        f[x][1]=max(f[x][1],f[to][2]+1);
        f[x][3]=max(f[x][3],f[x][0]+f[to][1]-1);
        f[x][3]=max(f[x][3],f[x][0]+f[to][2]-1);
        f[x][3]=max(f[x][3],f[x][2]+f[to][0]-1);
        f[x][3]=max(f[x][3],f[to][3]+deg[x]-1);
        f[x][3]=max(f[x][3],f[to][0]+deg[x]+ret-2);
        f[x][2]=max(f[x][2],f[x][0]+f[to][0]-1);
        f[x][0]=max(f[x][0],f[to][0]+deg[x]-1);
        ret=max(ret,max(f[to][2],f[to][1]));
    }
    return;
}
int main()
{
    freopen("treediagram.in","r",stdin);
    freopen("treediagram.out","w",stdout);
    T=read();x=read();
    while(T--)
    {
        ans=0;
        for(int i=1;i<=n;i++)deg[i]=h[i]=0;ei=0;
        n=read();for(int i=1;i<=x*2;i++)read();
        for(int i=2;i<=n;i++)
        {
            int x=read(),y=read();
            deg[x]++;deg[y]++;deg[i]--;
            add(x,y);add(y,x);
        }
        dfs(1,0);
        printf("%d\n",ans);
    }
    return 0;
}

T2 分手是祝愿(trennen)

Sol
考试的时候没时间,就随便写了一个\(k=n\)的部分分,结果\(80pts\),数据挺水。
考场上想过是不是最优次数状态转移,但是没什么时间就没去细想。
首先证明一个性质:每种灯的状态与最优解的解法一一对应。
显然对于一种灯状态,只需要从大到小枚举,当灯是亮的时候就拉动开关。这样能保证有唯一的最优解。
灯有\(2^n\)种状态,而解法也只有\(2^n\),因此每种状态与最优解解法一一对应。
我们假定从最优解需要\(i\)次转移到最优解需要\(i-1\)次的期望步数为\(f(i)\)。那么对于一次随机拉动,如果拉动的是最优解包含的开关,则到达\(i-1\)状态,如果不是,由于状态与最优解一一对应,所以这个开关必定再被拉一次,到达\(i+1\)状态。
那么就可以得到转移方程:

\[f(i)=\frac{i}{n}+\frac{n-i}{n} * (f(i)+f(i+1)+1) \]

化简可得

\[f(i)=\frac{n+(n-i)*f(i+1)}{i} \]

显然\(f(n)=1\),倒着递推即可。
首先按照之前证明性质的方法\(O(n \sqrt n)\)找出给定状态的最优解步数\(x\)。如果\(x\leq k\)则答案就是\(x\)(就这样80到手)
对于剩下的情况\(ans=\sum_{i=k+1}^{x}f(i)+k\),最后记得将答案乘\(n!\)即可。
Code

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=100010,p=100003;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=0;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return f?x:-x;
}
int inv,n,k,ans;
int a[maxn],f[maxn];
inline int ksm(int x,int mi)
{
    int an=1;
    while(mi)
    {
        if(mi&1)an=an*x%p;
        x=x*x%p;mi>>=1;
    }
    return an;
}
signed main()
{
    freopen("trennen.in","r",stdin);
    freopen("trennen.out","w",stdout);
    n=read();k=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=n;i>=1;i--)
    {
        if(!a[i])continue;
        ans++;int sq=sqrt(i);
        for(int j=1;j<=sq;j++)
        {
            if(i%j==0)
            {
                if(j*j!=i)a[j]^=1;a[i/j]^=1;
            }
        }
    }
    if(k>=ans)
    {
        for(int i=2;i<=n;i++)ans=ans*i%p;
        printf("%lld\n",ans);
        return 0;
    }
    f[n]=1;
    int rst=k;
    for(int i=n-1;i>k;i--)f[i]=(n+(n-i)*f[i+1]%p)*ksm(i,p-2)%p;
    for(int i=ans;i>k;i--)rst=(rst+f[i])%p;
    for(int i=2;i<=n;i++)rst=rst*i%p;
    printf("%lld\n",rst);
    return 0;
}

T3 寿司餐厅(sushi)

Sol
u1s1,我肯定想不出来这是网络流。看样子之前的题白做了。
首先给每种选取寿司区间和寿司类型设置一个节点。
每个寿司向它的类型连边,流量\(inf\)
每种寿司类型向汇点连边,流量\(m*a_i*a_i\)
每个寿司向汇点连边,流量\(a_i\)
这三个表示寿司花费。
对于一个区间:如果它的美味值为正,则从源点向它连边,流量\(b_{i,j}\),否则由它向汇点连边,流量\(-b_{i,j}\)
这个区间还要和其端点连边,流量\(inf\),表示选择该区间。
每个区间向相邻的区间连边,流量\(inf\)
最后跑一遍最小割,输出\(ans-sum\)
Code

//GPC,我来贺你代码啦,为你点赞! 
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=200010,maxm=2000010,inf=1e18;
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')f=0;c=getchar();}
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c&15),c=getchar();
    return f?x:-x;
}
struct edge
{
    int to,next,v;
}e[maxm];
int h[maxn],ei=1;
inline void add(int x,int y,int v)
{
    e[++ei]=(edge){y,h[x],v};h[x]=ei;
    e[++ei]=(edge){x,h[y],0};h[y]=ei;
    return;
}
int n,m,S,T,ans,sum;
int dep[maxn];
int a[maxn],b[1010][1010];
int gppc,gpc[1010][1010],gpppc[maxn];
bool vis[maxn];
queue<int>qu;
inline bool bfs()
{
    memset(dep,0,sizeof(dep));
    dep[S]=1;qu.push(S);
    while(!qu.empty())
    {
        int x=qu.front();qu.pop();
        for(int i=h[x];i;i=e[i].next)
        {
            int to=e[i].to;
            if(dep[to]||e[i].v==0)continue;
            dep[to]=dep[x]+1;
            qu.push(to);
        }
    }
    return dep[T]>0;
}
inline int dfs(int x,int maxflow)
{
    if(x==T)return maxflow;
    int flow=0;
    for(int i=h[x];i;i=e[i].next)
    {
        int to=e[i].to;
        if(dep[to]!=dep[x]+1||e[i].v==0)continue;
        int rst=dfs(to,min(maxflow-flow,e[i].v));
        if(rst==0){dep[to]=0;continue;}
        e[i].v-=rst;e[i^1].v+=rst;
        flow+=rst;
        if(flow==maxflow)break;
    }
    return flow;
}
inline void dinic()
{
    while(bfs())sum+=dfs(S,inf);
    return;
}
signed main()
{
    freopen("sushi.in","r",stdin);
    freopen("sushi.out","w",stdout);
    n=read();m=read();
    for(int i=1;i<=n;i++)a[i]=read();
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)gpc[j][i]=++gppc;
        for(int j=i;j<=n;j++)b[i][j]=b[j][i]=read();
    }
    for(int i=1;i<=n;i++)
    {
        if(vis[a[i]])continue;
        vis[a[i]]=true;
        gpppc[a[i]]=++gppc;
    }
    S=0;T=n+gppc+1;
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
    {
        if(vis[a[i]])continue;
        vis[a[i]]=true;
        add(gpppc[a[i]],T,m*a[i]*a[i]);
    } 
    for(int i=1;i<=n;i++)
    {
        add(gppc+i,gpppc[a[i]],inf);
        add(gppc+i,T,a[i]);
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i;j<=n;j++)
        {
            add(gpc[i][j],gppc+i,inf);
            add(gpc[i][j],gppc+j,inf);
            if(b[i][j]<0)add(gpc[i][j],T,-b[i][j]);
            else if(b[i][j]>0)
            {
                ans+=b[i][j];
                add(S,gpc[i][j],b[i][j]);
            }
            if(i!=j)
            {
                add(gpc[i][j],gpc[i+1][j],inf);
                add(gpc[i][j],gpc[i][j-1],inf);
            }
        }
    }
    dinic();
    printf("%lld\n",ans-sum);
    return 0;
}
posted @ 2021-09-16 21:31  wwlvv  阅读(47)  评论(0)    收藏  举报