周测题解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)$
这里有两个关键:
- $j$ 的范围
首先 $j≥1$ 应该很好理解:$dp[v][0]+dp[x][i-0]=dp[x][i]$,原状态,转移了个寂寞。
然后,$j<i$,如果 $j=i$,在子树上取了 $i$ 个,连原树的根都没取,子树是怎么取的?显然不合理。
- $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
暴搜大家应该都会写。
这里就是比一般的遍历图多了个数组,记录游览过的文明。
游览一个点前要判断:
- 这个点没到过
- 这个点的文明没访问过
- 这个点的文明和之前访问的都不冲突
然后就是 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;
}