冲刺国赛模拟 33

降智,同时看了半个上午的《查拉图斯特拉如是说》。有点晕。

喜报:又垫了!

染色

六个人写了三个写法。我是看着这道题就感觉是个点分树板子就冲了个点分树。点分树上维护子树内黑点到根的距离和和有多少个黑点,暴力跳加贡献。嘉然是题解做法树剖线段树。

三个人写了操作分块。但是好像并没有比点分树和树剖短。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
#define int long long
using namespace std;
int n,m;
struct node{
    int v,w,next;
}edge[200010];
int t,head[100010];
void add(int u,int v,int w){
    edge[++t].v=v;edge[t].w=w;edge[t].next=head[u];head[u]=t;
}
int rt,sum,size[100010],mx[100010],fa[100010];
int num,dfn[100010],dep[100010],F[100010],top[100010],son[100010],Dis[100010];
bool vis[100010];
void dfs1(int x,int f){
    size[x]=1;F[x]=f;dep[x]=dep[f]+1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            Dis[edge[i].v]=Dis[x]+edge[i].w;
            dfs1(edge[i].v,x);
            size[x]+=size[edge[i].v];
            if(size[son[x]]<size[edge[i].v])son[x]=edge[i].v;
        }
    }
}
void dfs2(int x,int f,int tp){
    dfn[x]=++num;top[x]=tp;
    if(son[x])dfs2(son[x],x,tp);
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=son[x]&&edge[i].v!=f)dfs2(edge[i].v,x,edge[i].v);
    }
}
int lca(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=F[top[x]];
    }
    if(dep[x]>dep[y])swap(x,y);
    return x;
}
int dis(int x,int y){
    return Dis[x]+Dis[y]-2*Dis[lca(x,y)];
}
void findrt(int x,int f){
    size[x]=1;mx[x]=0;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f&&!vis[edge[i].v]){
            findrt(edge[i].v,x);
            size[x]+=size[edge[i].v];
            mx[x]=max(mx[x],size[edge[i].v]);
        }
    }
    mx[x]=max(mx[x],sum-size[x]);
    if(mx[x]<mx[rt])rt=x;
}
void div(int x){
    findrt(x,0);
    vis[x]=true;
    for(int i=head[x];i;i=edge[i].next){
        if(!vis[edge[i].v]){
            sum=size[edge[i].v];rt=0;
            findrt(edge[i].v,x);
            fa[rt]=x;div(rt);
        }
    }
}
int val[100010][2],cnt[100010][2];
void update(int x){
    int ret=x;
    while(x)val[x][0]+=dis(x,ret),cnt[x][0]++,x=fa[x];
    x=ret;
    while(fa[x])val[x][1]+=dis(fa[x],ret),cnt[x][1]++,x=fa[x];
}
int query(int x){
    int ans=val[x][0],ret=x;
    while(fa[x]){
        ans+=val[fa[x]][0]-val[x][1]+dis(ret,fa[x])*(cnt[fa[x]][0]-cnt[x][1]);
        x=fa[x];
    }
    return ans;
}
int lastans;
signed main(){
    scanf("%lld%lld",&n,&m);sum=mx[0]=n;
    for(int i=2;i<=n;i++)scanf("%lld",&fa[i]),fa[i]++;
    for(int i=2;i<=n;i++){
        int d;scanf("%lld",&d);
        add(fa[i],i,d);add(i,fa[i],d);fa[i]=0;
    }
    dfs1(1,0);dfs2(1,0,1);
    findrt(1,0);findrt(rt,0);
    div(rt);
    for(int i=1;i<=n;i++)vis[i]=false;
    while(m--){
        int od,x;scanf("%lld%lld",&od,&x);x++;
        if(od==1){
            if(!vis[x])update(x),vis[x]=true;
        }
        else printf("%lld\n",query(x));
    }
    return 0;
}

寻宝游戏

咋全都会这题。

观察到它在多边形内的定义是光线投射,于是设出状态 \(dp_{x,y,s}\) 为走到 \((x,y)\),每个点引出的射线(这个只需要向一个方向引出斜率几乎为 \(0\) 的射线即可保证和任何端点不交)经过多边形的边次数奇偶性的状态为 \(s\) 的最短路程,转移可以直接 bfs。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <queue>
using namespace std;
const int dx[]={0,1,-1,0},dy[]={1,0,0,-1};
int n,m,cnt,val[10],x[10],y[10],dp[30][30][1<<8],sum[1<<8];
char s[30][30];
struct node{
    int x,y,s;
};
queue<node>q;
bool check(int x,int y){
    return x>=1&&x<=n&&y>=1&&y<=m&&s[x][y]!='#'&&!isdigit(s[x][y])&&s[x][y]!='B';
}
bool get(int id,int a,int b,int xx,int yy){
    if(a==xx)return 0;
    if(xx<a&&x[id]==a&&y[id]<b)return true;
    if(xx>a&&x[id]==xx&&y[id]<b)return true;
    return false;
}
int main(){
    scanf("%d%d",&n,&m);
    int stx,sty;
    for(int i=1;i<=n;i++){
        scanf("%s",s[i]+1);
        for(int j=1;j<=m;j++)if(isdigit(s[i][j]))cnt++,x[s[i][j]-'0']=i,y[s[i][j]-'0']=j;
    }
    for(int i=1;i<=cnt;i++)scanf("%d",&val[i]);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(s[i][j]=='S')stx=i,sty=j;
            if(s[i][j]=='B')x[++cnt]=i,y[cnt]=j,val[cnt]=-1000000;
        }
    }
    for(int i=1;i<=cnt;i++)sum[1<<i-1]=val[i];
    for(int i=1;i<(1<<cnt);i++)sum[i]=sum[i&(i-1)]+sum[i&-i];
    memset(dp,0x3f,sizeof(dp));
    q.push({stx,sty,0});dp[stx][sty][0]=0;
    while(!q.empty()){
        int x=q.front().x,y=q.front().y,s=q.front().s;q.pop();
        for(int i=0;i<4;i++){
            int xx=x+dx[i],yy=y+dy[i];
            if(check(xx,yy)){
                int tmp=s;
                for(int j=1;j<=cnt;j++)tmp^=get(j,x,y,xx,yy)<<j-1;
                if(dp[xx][yy][tmp]==0x3f3f3f3f){
                    dp[xx][yy][tmp]=dp[x][y][s]+1;
                    q.push({xx,yy,tmp});
                }
            }
        }
    }
    int ans=-0x3f3f3f3f;
    for(int i=0;i<(1<<cnt);i++)ans=max(ans,sum[i]-dp[stx][sty][i]);
    printf("%d\n",ans);
    return 0;
}

点分治

瞪不下去了就看尼采去了。结果赛后看了一眼题解写了十分钟切了。消愁。

吗了赛时没切结果石头剪刀布输了还得讲题,更消愁了。我是不是猜拳没赢过。

\(dp_{x,i}\)\(x\) 的子树的点分树中 \(x\) 深度为 \(i\) 的方案数。转移考虑合并子树:容易发现合并两棵点分树的时候只需要保证节点到根仍然是有序的。那么实际上就是把两棵点分树到根的路径归并一下,树形背包转移即可。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <iostream>
#include <vector>
using namespace std;
const int mod=1000000007;
int n;
struct node{
    int v,next;
}edge[10010];
int t,head[5010],C[5010][5010];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
int dp[5010][5010],size[5010];
void dfs(int x,int f){
    dp[x][1]=size[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs(edge[i].v,x);
            static int tmp[5010];
            for(int j=0;j<=size[x]+size[edge[i].v];j++)tmp[j]=0;
            for(int j=size[x];j>=1;j--){
                for(int k=size[edge[i].v];k>=0;k--){
                    tmp[j+k]=(tmp[j+k]+1ll*dp[x][j]*dp[edge[i].v][k]%mod*C[j+k-1][k])%mod;
                }
            }
            size[x]+=size[edge[i].v];
            for(int j=0;j<=size[x];j++)dp[x][j]=tmp[j];
        }
    }
    for(int i=size[x];i>=0;i--)dp[x][i]=(dp[x][i]+dp[x][i+1])%mod;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    C[0][0]=1;
    for(int i=1;i<=n;i++){
        C[i][0]=1;
        for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    dfs(1,0);
    printf("%d\n",dp[1][0]);
    return 0;
}
posted @ 2023-07-10 15:19  gtm1514  阅读(21)  评论(0编辑  收藏  举报