The 2019 ICPC Asia Shanghai Regional Contest

链接:https://ac.nowcoder.com/acm/contest/4370

B. Prefix Code (签到题)

题意:给定 n 个串,问是否存在两个串满足其中一个串是另一个串的前缀。不存在输出 Yes,存在输出 No。(字符串最大长度为 10 ,只包含字符 ‘0’ 到 ‘9’)

思路:字典树。在插入的时候直接判断,如果遇到 color > 0 的位置,说明原来有个串是当前串的前缀。如果没有重新创建新的节点,说明当前串是原来串的前缀。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int t,n;
string s;
int trie[maxn][20],color[maxn],no;
bool insert(string s)
{
    int len=s.size(),p=0;
    bool ok=1;
    int last=no;
    for(int i=0; i<len; ++i)
    {
        int c=s[i]-'0';
        if(!trie[p][c]) trie[p][c]=++no;
        p=trie[p][c];
        if(color[p]) ok=0;
    }
    if(last==no) ok=0;
    color[p]++;
    return ok;
}
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        memset(trie,0,sizeof(trie));
        memset(color,0,sizeof(color));
        no=0;
        scanf("%d",&n);
        bool ok=1;
        for(int i=1; i<=n; ++i)
        {
            cin>>s;
            if(!insert(s)) ok=0;
        }
        printf("Case #%d: %s\n",++Case,ok?"Yes":"No");
    }
    return 0;
}

D. Spanning Tree Removal (构造题)

题意:给定一个 n 个节点的完全图,每次删掉一棵生成树,问最多能够删除几次。输出删除的方案。

思路:一个完全图有 n ( n − 1 ) 2 \frac {n(n-1)} 2 2n(n1) 条边,一棵生成树需要 n -1 条边。因此猜测可以删除 n 2 \frac n2 2n 棵树。

  • 一开始自己的想法是:在删除第 i 棵树时,保证每个点剩余可用边数 x ,满足 x > n 2 − i x>\frac n2 -i x>2ni ,然后用并查集随意匹配,然后就错了,只能满足小部分数据
  • 正解是 Z 字型删边:先删 i ,然后是 i +1 ,然后是 i - 1,然后是 i + 2 ,依次类推。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5;
int t,n;
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        printf("Case #%d: %d\n",++Case,n/2);
        for(int i=0; i<n/2; ++i)
        {
            int f=1,u=i;
            for(int j=1; j<=n-1; ++j)
            {
                int v=(u+f*j+n)%n;
                f*=-1;
                printf("%d %d\n",u+1,v+1);
                u=v;
            }
        }
    }
    return 0;
}

E. Cave Escape (最大生成树)

题意:给定一个 n × m n\times m n×m 的矩阵,起点在 (sx,sy) 终点在 (ex,ey)。每个点都有价值 V i j V_{ij} Vij 。假设从 (a,b) 走到 (c,d)且(c,d)未访问时,可以获得 V a b × V c d V_{ab}\times V_{cd} Vab×Vcd 的价值,只能按相邻的格子走,且一个点可以重复经过,问能够获得的最大价值是多少?( 1 ≤ n , m ≤ 1000 , 1 ≤ V i j ≤ 100 1\le n ,m \le 1000 ,1\le V_{ij} \le 100 1n,m1000,1Vij100

思路:可以重复经过一个点,所以起点和终点是谁都无所谓。

  • 首先可以想到每个点向与它相连且权值最大的点连边。然后就会形成很多个联通块,接下来的问题就是,连通块和连通块之间找一条最大的边来相连。
  • 这样一套下来,不就是最大生成树吗?那就完事了。
  • 自己写的挺卡常的,就学习了一下网上的做法,观察到权值最多只有 10000 。可以按照权值来遍历。(大概是省掉了排序的时间)
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1000+5;

int t,n,m,sx,sy,ex,ey;
int A,B,C,P;
int x[maxn*maxn];
int cnt;
vector<pair<int,int> > e[10010];

int pa[maxn*maxn];
int find(int x)
{
    return x==pa[x]?x:pa[x]=find(pa[x]);
}

ll solve()
{
    for(int i=1; i<=n*m; ++i) pa[i]=i;
    int tot=0;
    ll ans=0;
    for(int i=10000; i>=0; --i)
    {
        for(auto x: e[i])
        {
            int u=x.fi,v=x.se;
            int ru=find(u),rv=find(v);
            if(ru==rv) continue;
            ans+=i;
            pa[ru]=rv;
            tot++;
            if(tot==n*m-1) break;
        }
        if(tot==n*m-1) break;
    }
    return ans;
}
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
        scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
        for(int i=3; i<=n*m; ++i) x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
        cnt=0;
        for(int i=0; i<=10000; ++i) e[i].clear();
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=m; ++j)
            {
                int u=(i-1)*m+j;
                int v1=(i-1)*m+j-1;
                int v2=(i-2)*m+j;
                if(v1>=1&&v1%m!=0) e[x[u]*x[v1]].push_back({u,v1});
                if(v2>=1) e[x[u]*x[v2]].push_back({u,v2});
            }
        }
        printf("Case #%d: %lld\n",++Case,solve());
    }
    return 0;
}

普通写法,时间慢一点。

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1000+5;

int t,n,m,sx,sy,ex,ey;
int A,B,C,P;
int x[maxn*maxn];
int cnt;

struct Edge
{
    int u,v,w;
    bool operator<(const Edge& b) const
    {
        return w>b.w;
    }
} edges[maxn*maxn*4];

int pa[maxn*maxn];
int find(int x)
{
    return x==pa[x]?x:pa[x]=find(pa[x]);
}
ll solve()
{
    for(int i=1; i<=n*m; ++i) pa[i]=i;
    sort(edges+1,edges+1+cnt);
    ll ans=0;
    int tot=0;
    for(int i=1; i<=cnt; ++i)
    {
        int u=edges[i].u,v=edges[i].v;
        int ru=find(u),rv=find(v);
        if(ru==rv) continue;
        ans+=edges[i].w;
        pa[ru]=rv;
        tot++;
        if(tot==n*m-1) break;
    }
    return ans;
}
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
        scanf("%d%d%d%d%d%d",&x[1],&x[2],&A,&B,&C,&P);
        for(int i=3; i<=n*m; ++i) x[i]=(A*x[i-1]+B*x[i-2]+C)%P;
        cnt=0;
        for(int i=1; i<=n; ++i)
        {
            for(int j=1; j<=m; ++j)
            {
                int u=(i-1)*m+j;
                int v1=(i-1)*m+j-1;
                int v2=(i-2)*m+j;
                if(!x[u]) continue;
                if(v1>=1&&v1%m!=0&&x[v1]) edges[++cnt]= {u,v1,x[u]*x[v1]};
                if(v2>=1&&x[v2]) edges[++cnt]= {u,v2,x[u]*x[v2]};
            }
        }
        printf("Case #%d: %lld\n",++Case,solve());
    }
    return 0;
}

F. A Simple Problem On A Tree (树链剖分裸题)

题意:给定一颗树,每个点带点权,维护四个操作

  • 1 u v w : 将 u 、v 路径上的权值修改为 w
  • 2 u v w : 将 u 、v 路径上的权值加上 w
  • 3 u v w : 将 u 、v 路径上的权值乘上 w
  • 4 u v : 询问 u 、v 路径上点权的立方和,即求 ∑ x w x 3 \sum_x w_x^3 xwx3

思路:树链剖分维护区间的和,平方和,以及立方和

#include <bits/stdc++.h>
#define ls (rt<<1)
#define rs (rt<<1|1)
#define ll long long
using namespace std;
const int maxn=1e5+5,mod=1e9+7;

int t,n,q;
int val[maxn];
vector<int> e[maxn];
int sum1[maxn<<2],sum2[maxn<<2],sum3[maxn<<2];
int lazyAdd[maxn<<2],lazyMul[maxn<<2];

int fa[maxn],sz[maxn],son[maxn],depth[maxn];
int dfn[maxn],id[maxn],top[maxn],times;

void add(int &x,int y)
{
    x+=y;
    if(x>=mod) x-=mod;
}
void mul(int &x,int y)
{
    x=1ll*x*y%mod;
}
void dfs1(int u)
{
    sz[u]=1;
    for(auto v: e[u])
    {
        if(v==fa[u]) continue;
        depth[v]=depth[u]+1;
        fa[v]=u;
        dfs1(v);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int x)
{
    dfn[u]=++times;
    id[times]=u;
    top[u]=x;
    if(!son[u]) return;
    dfs2(son[u],x);
    for(auto v: e[u])
    {
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
void solve(int rt,int len,int a,int b)
{
    mul(lazyMul[rt],a);
    mul(lazyAdd[rt],a);
    add(lazyAdd[rt],b);
    if(a!=1)
    {
        mul(sum1[rt],a);
        mul(sum2[rt],1ll*a*a%mod);
        mul(sum3[rt],1ll*a*a%mod*a%mod);
    }
    if(b!=0)
    {
        int b2=1ll*b*b%mod,b3=1ll*b2*b%mod;
        add(sum3[rt],1ll*len*b3%mod);
        add(sum3[rt],3ll*sum2[rt]%mod*b%mod);
        add(sum3[rt],3ll*sum1[rt]%mod*b2%mod);
        add(sum2[rt],2ll*sum1[rt]*b%mod);
        add(sum2[rt],1ll*len*b2%mod);
        add(sum1[rt],1ll*len*b%mod);
    }
}
void pushUp(int rt)
{
    sum1[rt]=(sum1[ls]+sum1[rs])%mod;
    sum2[rt]=(sum2[ls]+sum2[rs])%mod;
    sum3[rt]=(sum3[ls]+sum3[rs])%mod;
}
void pushDown(int rt,int L,int R)
{
    int mid=(L+R)>>1;
    int a=lazyMul[rt],b=lazyAdd[rt];
    solve(ls,mid-L+1,a,b);
    solve(rs,R-mid,a,b);
    lazyAdd[rt]=0;
    lazyMul[rt]=1;
}
void build(int rt,int L,int R)
{
    lazyAdd[rt]=0;
    lazyMul[rt]=1;
    if(L==R)
    {
        int w=val[id[L]];
        sum1[rt]=w;
        sum2[rt]=1ll*w*w%mod;
        sum3[rt]=1ll*sum2[rt]*w%mod;
        return;
    }
    int mid=(L+R)>>1;
    build(ls,L,mid);
    build(rs,mid+1,R);
    pushUp(rt);
}
void update(int rt,int l,int r,int L,int R,int a,int b)
{
    if(l<=L&&R<=r)
    {
        solve(rt,R-L+1,a,b);
        return;
    }
    pushDown(rt,L,R);
    int mid=(L+R)>>1;
    if(l<=mid) update(ls,l,r,L,mid,a,b);
    if(r>mid) update(rs,l,r,mid+1,R,a,b);
    pushUp(rt);
}
int query(int rt,int l,int r,int L,int R)
{
    if(l<=L&&R<=r) return sum3[rt];
    pushDown(rt,L,R);
    int ans=0;
    int mid=(L+R)>>1;
    if(l<=mid) add(ans,query(ls,l,r,L,mid));
    if(r>mid) add(ans,query(rs,l,r,mid+1,R));
    pushUp(rt);
    return ans;
}
void updatePath(int u,int v,int a,int b)
{
    while(top[u]!=top[v])
    {
        if(depth[top[u]]<depth[top[v]]) swap(u,v);
        update(1,dfn[top[u]],dfn[u],1,n,a,b);
        u=fa[top[u]];
    }
    if(depth[u]>depth[v]) swap(u,v);
    update(1,dfn[u],dfn[v],1,n,a,b);
}
int queryPath(int u,int v)
{
    int ans=0;
    while(top[u]!=top[v])
    {
        if(depth[top[u]]<depth[top[v]]) swap(u,v);
        add(ans,query(1,dfn[top[u]],dfn[u],1,n));
        u=fa[top[u]];
    }
    if(depth[u]>depth[v]) swap(u,v);
    add(ans,query(1,dfn[u],dfn[v],1,n));
    return ans;
}
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; ++i) e[i].clear(),son[i]=0;
        times=0;
        for(int i=1; i<=n-1; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        for(int i=1; i<=n; ++i) scanf("%d",&val[i]);

        dfs1(1);
        dfs2(1,1);
        build(1,1,n);
        int op,u,v,w;
        printf("Case #%d:\n",++Case);
        scanf("%d",&q);
        while(q--)
        {
            scanf("%d",&op);
            if(op==4)
            {
                scanf("%d%d",&u,&v);
                printf("%d\n",queryPath(u,v));
            }
            else
            {
                scanf("%d%d%d",&u,&v,&w);
                if(op==1) updatePath(u,v,0,w);
                else if(op==2) updatePath(u,v,1,w);
                else if(op==3) updatePath(u,v,w,0);
            }
        }
    }
    return 0;
}

H. Tree Partition (二分)

题意:给定一颗 n 个节点的树,每个点带点权,让你分成 k 份,使得连通块中最大的点权和最小,输出这个最小值。

思路:显然二分答案。需要同叶节点开始往根走,边走边累积份数。

  • 一开始写过拓扑排序,写到一半发现,拓扑排序不能保证根节点和子节点相连,只能用 dfs
  • 需要保证的是:根节点和点权和比较小的子节点相连。其余的子节点直接另开一份。字节点返回两个值,一个是子节点构成的份数 cnt 和 以子节点为根构成的点权和 val 。将当前点 u 和子节点返回的较小的点权 val 结合成一份即可。
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+5;

int t,n,k;
vector<int> e[maxn];
int val[maxn];
int tot;
ll now;

pair<ll,ll> dfs(int u,int fa,ll mid)
{
    ll cnt=0;
    vector<ll> ans;
    for(auto v: e[u])
    {
        if(v==fa) continue;
        auto res=dfs(v,u,mid);
        ans.push_back(res.se);
        cnt+=res.fi;
    }
    sort(ans.begin(),ans.end());
    ll now=val[u];
    int n=ans.size();
    int pos=-1;
    for(int i=0; i<n; ++i)
    {
        if(now+ans[i]>mid)
        {
            pos=i;
            break;
        }
        else now+=ans[i];
    }
    if(pos!=-1) cnt+=n-pos;
    return {cnt,now};
}

int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&k);
        for(int i=1; i<=n; ++i) e[i].clear();
        for(int i=1; i<=n-1; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
            e[v].push_back(u);
        }
        ll L=0,R=1e15;
        for(int i=1; i<=n; ++i) scanf("%d",&val[i]),L=max(L,1ll*val[i]);
        while(L<R)
        {
            ll mid=(L+R)>>1;
            auto res=dfs(1,0,mid);
            if(res.se) res.fi++;
            if(res.fi>k) L=mid+1;
            else R=mid;
        }
        printf("Case #%d: %lld\n",++Case,L);
    }
    return 0;
}

K. Color Graph (奇环)

题意:给定一个 n 个点 m 条边的无向联通图。让你对边染色,使得染完色之后不存在奇环,且染的边数最大。输出这个最大的边数

思路:不存在奇环的图是二分图。注意到 n 只有 16 ,显然是想让你暴力。

  • 枚举一下一边的点集,然后遍历所有边,统计答案即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=200;
int t,n,m;
int visit[maxn];
pair<int,int> edges[maxn];
int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        for(int i=1; i<=m; ++i)
        {
            int u,v;
            scanf("%d%d",&u,&v);
            edges[i]= {u,v};
        }
        int ans=0;
        for(int i=0; i<(1<<n); ++i)
        {
            memset(visit,0,sizeof(visit));
            for(int j=1; j<=n; ++j)
                if(i>>j-1&1) visit[j]=1;
            int res=0;
            for(int j=1; j<=m; ++j)
            {
                int u=edges[j].first,v=edges[j].second;
                if(visit[u]^visit[v]) res++;
            }
            ans=max(ans,res);
        }
        printf("Case #%d: %d\n",++Case,ans);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值