周测题解8.23

这个国王的新连麦键就nm尴尬

还有T3正解是暴搜T4是模拟,这里主要说一下T1和T2。

T1

一个树形DP。

设计状态

设 $dp[i][j]$ 表示在以 $i$ 为根的子树上取 $j$ 个点的最大收益。

这里就会发现,这其实是一个森林:所有没有先修课的课都是一棵树的根。

那么我们就可以直接让输入的 $0$ 作为这些“根”的父节点,而且 $0$ 点是必学的(否则没办法学其他的科)。

所以一共要学 $m+1$ 门课,即答案为 $dp[0][m+1]$。

初始状态

这个也不难想。从 $i$ 点为根的子树上取一个点,收益就是 $s_i$。

即 $dp[i][1]=s_i(1≤i≤n)$。

状态转移

一棵树的状态从哪来?当然是从子树来了。下面称 $x$ 的一个子节点为 $v$。

则有状态转移方程:$dp[x][i]=max(dp[x][i],dp[v][j]+dp[x][i-j])(1≤x≤n,1≤i≤m+1,1≤j<i)$

这里有两个关键:

  1. $j$ 的范围

首先 $j≥1$ 应该很好理解:$dp[v][0]+dp[x][i-0]=dp[x][i]$,原状态,转移了个寂寞。

然后,$j<i$,如果 $j=i$,在子树上取了 $i$ 个,连原树的根都没取,子树是怎么取的?显然不合理。

  1. $i$ 的枚举顺序

这其实类似01背包。

如果 $dp[x][i-j]$ 包括 $dp[v][j]$,那就重复包含了(看方程 $max$ 的第二个参数)。

所以要求更新 $dp[x][i]$ 时,$dp[x][i-j]$ 没有更新。

即大的比小的早更新,$i$ 要倒序枚举。

代码

#include <iostream>
#include <list>
using namespace std;
list<int> edge[301];int n, m, dp[301][301];
void dfs(int x)
{
    for(auto &v : edge[x])
    {
        dfs(v);
        for(int i = m + 1;i >= 1;--i)
            for(int j = 1;j < i;++j)
                dp[x][i] = max(dp[x][i], dp[v][j] + dp[x][i - j]);
    }
}
int main()
{
    cin >> n >> m;
    for(int i = 1, v;i <= n;++i)
        cin >> v >> dp[i][1], edge[v].push_back(i);
    dfs(0);
    cout << dp[0][m + 1];
    return 0;
}

T2

分层图模板题。

建图

本题唯一难点。

想象一下原图分成上下分离的,平行的若干层。

平行的层中对应点有 0 权边。

每条边在相邻层的对应点上也有 0 权边。

需要算从第一层的 $s$ 到最后一层的 $t$。

相信大家不难看出,0 权边模拟的是免费机票。

每用一次免费机票,就往下一层。

如果不用就直接到下一层的对应点,也存在 0 权边。

那总共最多用 $k$ 次,总共 $k+1$ 层。

那这题就没了。

代码

可以用 $x+yn$ 表示 $x$ 点在第 $y$ 层上的编号,最上面是 0 层。

#include <iostream>
#include <list>
#include <queue>
#include <functional>
#include <utility>
#include <cstring>
using namespace std;
list<pair<int, int> > edge[2000001];int n, m, k, s, t, u, v, w, dis[2000001], vis[2000001];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > q;
void add(int u, int v, int w) {edge[u].push_back(make_pair(w, v));}
int main()
{
    cin >> n >> m >> k >> s >> t;
    for(int i = 0;i < k;++i)
        for(int j = 0;j < n;++j)
            add(j + i * n, j + (i + 1) * n, 0);
    for(int i = 0;i < m;++i)
    {
        cin >> u >> v >> w;
        for(int j = 0;j < k;++j)
        {
            add(u + j * n, v + j * n, w);
            add(v + j * n, u + j * n, w);
            add(u + j * n, v + (j + 1) * n, 0);
            add(v + j * n, u + (j + 1) * n, 0);
        }
        add(u + k * n, v + k * n, w);
        add(v + k * n, u + k * n, w);
    }
    memset(dis, 0x3f, sizeof dis);
    q.push(make_pair(0, s));dis[s] = 0;
    while(!q.empty())
    {
        int u = q.top().second;q.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(auto &i : edge[u])
            if(dis[i.second] > dis[u] + i.first)
            {
                dis[i.second] = dis[u] + i.first;
                q.push(make_pair(dis[i.second], i.second));
            }
    }
    cout << dis[t + k * n];
    return 0;
}

T3

暴搜大家应该都会写。

这里就是比一般的遍历图多了个数组,记录游览过的文明。

游览一个点前要判断:

  1. 这个点没到过
  2. 这个点的文明没访问过
  3. 这个点的文明和之前访问的不冲突

然后就是 dfs 板子了。

这是学长的代码(自己加的注释):

#include<bits/stdc++.h>
#define R register
#define ll long long 
#define N 4010
#define p 20000528
#define inf 0x3f3f3f3f
#pragma GCC optimize(3) //手动开O3最好不要学
using namespace std;
inline int read() 
{
    R int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
    return x*f;
}
void out(R int x)
{
    if(x<0)putchar('-'),x=-x;
    if(x>9)out(x/10);putchar(x%10+'0');
}
int n,k,m,s,t;
int c[N],a[N][N],head[N],net[N],to[N],tot,w[N];
void add(R int x,R int y,R int z)
{
    to[++tot]=y;
    w[tot]=z;
    net[tot]=head[x];
    head[x]=tot;
}
int ans=inf;
int vc[N],v[N],cnt;
void dfs(R int x,R int y,R int stp)
{
    if(stp>ans)return; //剪枝,如果现在搜到的比当前答案大,就没必要搜了
    if(x==y)ans=min(stp,ans); //到了
    for(R int i=head[x];i;i=net[i])
    {
        int vj=to[i];
        int f=0;
        for(R int j=1;j<=k;j++)
        if(vc[j]&&a[j][c[vj]]) //如果之前访问过的文明和下一个冲突
        {
            f=1;break;
        }
        if(!v[vj]&&!vc[c[vj]]&&!f) //三个条件
        {
            v[vj]=1,vc[c[vj]]=1;
            dfs(vj,y,stp+w[i]);
            v[vj]=0,vc[c[vj]]=0;
        }
    }
}
int main()
{
    n=read(),k=read(),m=read(),s=read(),t=read();
    for(R int i=1;i<=n;i++)c[i]=read();
    for(R int i=1;i<=k;i++)
        for(R int j=1;j<=k;j++)
            a[i][j]=read();
    for(R int i=1;i<=m;i++) {
        R int x,y,z;
        x=read(),y=read(),z=read();
        add(x,y,z);
        add(y,x,z);
    }
    v[t]=1,vc[c[t]]=1;
    dfs(t,s,0);
    out(ans==inf?-1:ans);
    return 0;
}   

T4

没啥好说的,纯模拟题。

这里只需要注意越界问题就行了(即[减后+n防负数]和[%n防溢出])

#include <iostream>
#include <string>
using namespace std;
struct p
{
    bool f;string s;
}a[100001];
int n, m, ans, x, y;
int main()
{
    cin >> n >> m;
    for(int i = 0;i < n;++i)
        cin >> a[i].f >> a[i].s;
    for(int i = 0;i < m;++i)
    {
        cin >> x >> y;
        if(!x && !a[ans].f) ans = (ans + n - y) % n;
        else if(!x && a[ans].f) ans = (ans + y) % n;
        else if(x && !a[ans].f) ans = (ans + y) % n;
        else if(x && a[ans].f) ans = (ans + n - y) % n;
    }
    cout << a[ans].s;
    return 0;
}
posted @ 2021-08-23 17:01  5k_sync_closer  阅读(1)  评论(0编辑  收藏  举报  来源