[2018多省省队联测] Day 1 解题报告

本文探讨了在特定约束条件下,通过使用哈希记录状态、深度优先搜索等技术解决两个玩家之间的策略游戏问题。此外,还介绍了如何利用线段树处理具有特殊条件的树形结构数据,以及一种新颖的方法来解决基于树的连通块问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

T1 一双木棋chess
这里写图片描述
n,m<=10
Alice想最大化差值,Bob想最小化差值。发现棋子呈阶梯状排布,总状态数阶梯状排布,阶乘复杂度。
哈希记录状态后爆搜,记录当前是谁的回合,按照自身的决策去最大化/最小化价值。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int base=15;
const ll INF=2e18;

map<ll,int>ma;
int st[15],A[15][15],B[15][15],n,m;

ll range;
ll gethash(){
    ll ans=0;
    for(int i=1;i<=n;i++)ans=ans*base+st[i];
    return ans;
}

ll dfs(ll S,int ty){
    if(ma[S])return ma[S];
    if(S==range)return 0;
    ll ans=ty==0?-INF:INF;
    for(int i=1;i<=n;i++){
        if(st[i]<st[i-1]){
            st[i]++;
            if(ty==0)ans=max(ans,dfs(gethash(),ty^1)+A[i][st[i]]);
            else ans=min(ans,dfs(gethash(),ty^1)-B[i][st[i]]);
            st[i]--;
        }
    }
    return ma[S]=ans;
}

int main(){
//  freopen("chess.in","r",stdin);
//  freopen("chess.out","w",stdout);
    memset(st,0,sizeof(st));
    scanf("%d%d",&n,&m);st[0]=m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&A[i][j]);
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&B[i][j]);
        }
    }
    range=0;
    for(int i=1;i<=n;i++)range=range*base+m;
    cout<<dfs(0,0);
}

T2.IIIDX
这里写图片描述

读题题意即 有一些形态相近的树组成的森林,树的形态给定,每个节点从给定权值中赋予一个权值,使得每棵树都是一个小根堆,并且要求字典序最大的方案。

把每棵树根连向0号节点形成一棵树。很直接的思路,排序后,对于同层次的树,右边的填小的留出位置给左边。左边填可控区间内最小的。
但是题目中有一个很奇怪的条件就是,数值可能会相同。。遇到奇怪的条件想一下反例。发现这样做在有相同时是合法的而不是最优的。
我们考虑一个排好序的序列,从中找出一个点填入,使这个点的值尽量大。提前就要预留好他子树中的位置,在有位置的情况下,找最左边的(从大到小排序)。然后他右边的节点都少了这些点的位置。对于一堆权值相同的节点,为了保证正确性,我们取这里面最右边的节点。
这个操作是可以用线段树实现的。
对于他父亲已经预留了位置,在查询第一个孩子时要把这个操作还原回去。
线段树叶子记录每个节点左边包括自身还剩几个空节点。这个具有单调性。
取一个min在线段树上二分就可以得到答案、

#include<bits/stdc++.h>
using namespace std;

const int MAXN=5e5+5;

struct edge{
    int to,next;
}e[MAXN<<1];

int head[MAXN],cnt=0;
inline void add(int u,int v){e[++cnt]=(edge){v,head[u]},head[u]=cnt;}

inline int read(){
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}

bool cmp(int a,int b){
    return a>b;
}

int size[MAXN],d[MAXN],after[MAXN],fa[MAXN],output[MAXN],n;
double kk;

void dfs(int u){
    size[u]=1;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        dfs(v);
        size[u]+=size[v];
    }
}

struct xds{
    #define lson (o<<1)
    #define rson (o<<1|1)
    int minv[MAXN<<2],lzt[MAXN<<2];
    inline void pushup(int o){
        minv[o]=min(minv[lson],minv[rson]);
    }
    inline void pushdown(int o){
        if(lzt[o]){
            lzt[lson]+=lzt[o];lzt[rson]+=lzt[o];
            minv[lson]+=lzt[o];minv[rson]+=lzt[o]; 
            lzt[o]=0;
        }
    }
    inline void build(int o,int l,int r){
        if(l==r){minv[o]=l;return;}
        int mid=l+r>>1;
        build(lson,l,mid);build(rson,mid+1,r);
        pushup(o);
    }
    inline void change(int o,int l,int r,int ql,int qr,int val){
        if(ql<=l&&qr>=r){minv[o]+=val;lzt[o]+=val;return;}
        int mid=l+r>>1;
        pushdown(o);
        if(ql<=mid)change(lson,l,mid,ql,qr,val);
        if(qr>=mid)change(rson,mid+1,r,ql,qr,val);
        pushup(o);
    }
    inline int find(int o,int l,int r,int k){
        if(l==r){return minv[o]>=k?l:l+1;}
        pushdown(o);
        int mid=l+r>>1;
        if(k>minv[rson])return find(rson,mid+1,r,k);
        return find(lson,l,mid,k);
    }
    #undef lson
    #undef rson
}T;

int main(){
//  freopen("iiidx.in","r",stdin);
//  freopen("iiidx.out","w",stdout);
    scanf("%d%lf",&n,&kk);
    for(int i=1;i<=n;i++){
        d[i]=read();
    }
    for(int i=n;i>=1;i--){
        int p=int(i/kk);
        fa[i]=p;
        add(p,i);
    }
    sort(d+1,d+1+n,cmp);
    for(int i=n;i>=1;i--)if(d[i]==d[i+1])after[i]=after[i+1]+1; else after[i]=0; 
    T.build(1,1,n);
    dfs(0);
//  for(int i=1;i<=n;i++)cout<<fa[i]<<" "<<size[i]<<" "<<after[i]<<endl;
    for(int i=1;i<=n;i++){
        if(fa[i]&&fa[i]!=fa[i-1])T.change(1,1,n,output[fa[i]],n,size[fa[i]]-1);
        int p=T.find(1,1,n,size[i]);
        p+=after[p];after[p]++;p-=after[p]-1;
        output[i]=p;
        T.change(1,1,n,p,n,-size[i]);
    }
    for(int i=1;i<=n;i++)printf("%d ",d[output[i]]);
    puts("");
    return 0;
}

T3 秘密袭击coat
这里写图片描述
标算不会,有一种优美的暴力。
题意为 给一棵树,从中选出所有不相同的元素个数大于k的连通块,求这些连通块中第k大元素权值和。
答案对64123取模。

我们考虑分别计算每个点的答案。
如果这个点在块中为第k大的连通块有v个。对答案的贡献为vs。
问题转化为求出每个点是第k大元素的连通块个数。
枚举每个点作为根。
如果这个点rank<k 可以直接剪掉。
否则统计答案。
因为连通块的信息是跨子树的,所以我们不能像通常一下子树上传。还要从父节点下传信息,再子树更新信息。
如果下传时他碰到了一个权值比他大的节点v,v与root所在连通块中,root为第k大的方案数等于 root与v的father连通块中,root为第k-1大的方案数。若v权值比root小则为k大的方案数。
子节点信息上传更新父节点计算答案即可。这样在点的权值均不相同时显然是对的。
若点的权值相同,我们可以强行按标号顺序区分权值大小,其余做法同上。

复杂度分析:
只有 nkn−k 个点 每个点作为根进行 dfsdfs。每次dfs遍历 nn 个点,每个点上传加下传复杂度O(k)O(k)
整体复杂度 O(n2knk2)O(n2k−nk2) 二次函数当 kk 取得 n/2n/2 时复杂度最差为 O(n3/2n3/4)=O(n3/4)O(n3/2−n3/4)=O(n3/4)
因为良心出题人并没有卡,所以可以A。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll; 
const int MAXN=2000;
const int MOD=64123;
ll f[MAXN][MAXN];

struct edge{
    int to,next;
}e[MAXN<<1];
int head[MAXN],val[MAXN],cnt=0,V,n,m,k,w;
inline void add(int u,int v){
    e[++cnt]=(edge){v,head[u]},head[u]=cnt;
    e[++cnt]=(edge){u,head[v]},head[v]=cnt;
}

inline ll ADD(ll x,ll y){
    x+=y;if(x>=MOD)x-=MOD;
    return x;
}

void dfs(int u,int fa){
    if(val[u]>val[V]||(val[u]==val[V]&&u>V))for(int i=1;i<=k;i++)f[u][i]=f[fa][i-1];
    else for(int i=1;i<=k;i++)f[u][i]=f[fa][i];
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v==fa)continue;
        dfs(v,u);
        for(int j=1;j<=n;j++)f[u][j]=ADD(f[u][j],f[v][j]);
    }

}

ll ans=0;

void calc(int p){
    //memset(f,0,sizeof())
    int tim=1;
    for(int i=1;i<=n;i++)if(val[i]>val[p]||(val[i]==val[p]&&i>p))tim++;
    if(tim<k)return;
    V=p;
    for(int i=0;i<=n;i++)f[p][i]=0;
    f[p][1]=1;
    for(int i=head[p];i;i=e[i].next){
        dfs(e[i].to,p);
        for(int j=1;j<=n;j++)f[p][j]=ADD(f[p][j],f[e[i].to][j]); 
    }
    //cout<<f[p][3]<<" "<<p<<endl;
    ans+=val[p]*f[p][k];
}

int main(){
    //freopen("coat.in","r",stdin);
    //freopen("coat.out","w",stdout);
    scanf("%d%d%d",&n,&k,&w);   
    for(int i=1;i<=n;i++){
        scanf("%d",&val[i]);
    }
    for(int i=1;i<n;i++){
        int u,v;
        scanf("%d%d",&u,&v);
        add(u,v);
    }
    for(int i=1;i<=n;i++){
        calc(i);
    }
    cout<<ans%MOD<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值