2023.9.19训练补题

1.Problem - 1739C - Codeforces

这道题不是很好理解,给出的操作很多。

题意简述:给你一个树的结构,让你给这个树赋点权(1∼n 的排列)。

然后每次删掉一个没有儿子(或儿子都被删去)的点,将这个点的点权加入答案序列中;如果该点父亲的点权小于该点的点权,则用该点的点权更新父亲点权。

 对于每个节点所在的子树有两种情况:

  1. 包括了根节点,此时最长的不降子序列中都是子树内的最小值,长度即为最小值对应节点到根路径上的节点个数。把最小值放到子树中最深的点即可取到最大值。
  2. 不包括根节点,显然可以通过调整初始权值使得:第一棵子树内的权值都小于第二棵,第二棵内的都小于第三棵,以此类推。这样答案是所有子树内答案的和。

对这两种情况取一个 max⁡max,做树形 dp 即可。

#include
using namespace std;
const int N=2e5+10;
int n,f[N][2];
int tot,head[N],ver[N],nxt[N];
void add(int u,int v){ver[++tot]=v,nxt[tot]=head[u],head[u]=tot;}
void dfs(int x){
	f[x][1]=1;
	for(int i=head[x],y;i;i=nxt[i]){
		y=ver[i];
		dfs(y);
		f[x][1]=max(f[x][1],f[y][1]+1);
		f[x][0]+=max(f[y][1],f[y][0]);
	}
}
int main()
{
   scanf("%d",&n);
   for(int i=2,p;i<=n;++i) scanf("%d",&p),add(p,i);
   dfs(1);
   printf("%d\n",max(f[1][0],f[1][1]));
    return 0;
}

反思总结:

1.操作很多,需要化简、提取特征,总结出这个题需要操作
2.什么时候是最优的 ,这个需要详细分析;
3.树上dp需强化

2.Problem - 1739C - Codeforces

考察知识点:
博弈、构造、dp、组合数(使用快速幂和逆元)

题目大意:

游戏牌堆中含偶数 n 张牌,每张牌上的数字不同,且大小在 1 到 n 之间(即给定 1 到 n 的全排列牌,又称 permutation)。两名玩家 A、B 都会在开局分得牌堆中的 \frac{n}{2}​ 张牌,手牌互异,点数随机。 首先玩家 A 出牌,对手 B 应牌(对手应牌点数需比出牌者大,且丢弃),然后玩家 B 出牌,对手 A 应牌,依次轮转,直至一人无法应牌判定其为输(无更大牌可应),或双方手牌为空判定平局(双方皆空手无牌可出)。 给定牌的张数 n,求能使 A 获胜的发牌方式数、能使 B 获胜的发牌方式数、能使双方平局的发牌方式数。

#include
using namespace std;
typedef long long ll;
#define M 998244353
ll t,f[109],g[109],p[109];
ll qp(ll a,ll b){
	if(b==1) return a;
	if(b==0) return 1;
	ll ans=1;
	if(b%2) b--,ans*=a;
	ll tmp=qp(a,b/2);
	ans*=tmp%M*tmp%M;
	return ans%M;
}
ll c(ll a,ll b){
	return p[a]%M*qp(p[b],M-2)%M*qp(p[a-b],M-2)%M; 
}
int main(){
	std::ios::sync_with_stdio(false);
	cin>>t;
	f[2]=1,p[1]=1;
	for(ll i=2;i<=100;i++) p[i]=p[i-1]*i%M;
	for(ll i=4;i<=100;i+=2){
		f[i]=g[i-2];
		f[i]+=(c(i-1,i/2-1))%M;
		f[i]%=M;
		g[i]=(c(i,i/2)%M-f[i]+M-1)%M;
	}
	while(t--){
		ll n;
		cin>>n;
		cout<

3.Problem - 1689C - Codeforces

树形dp+dfs

题目大意:

有一棵以 11 为根、有 n 个节点的二叉树。树根现在遭受了感染,在之后的每一秒钟,你能够去除树的一个节点,之后感染的节点向未感染的相邻节点传染。求能够保住的节点数的最大值(显然去除的节点不算保住)。

思考解法:

我们当然想尽可能多的保留节点,而且每一次肯定是删除被病毒感染的节点的某个子节点。

而删除之后我们的问题就可以转化为它的另一个儿子(注意为二叉树)的根被感染病毒能保留多少节点的子问题

dp[i] 表示在以 i 为根的子树中,若 i 被感染病毒,最多能保留多少节点。

我们就首先来分别考虑究竟删除哪一个子节点。

假设删除 lson,那么相当于在 lson 的子树里我们可以保留下 size[lson]−1 个节点,size[i] 代表以 i 为根的子树的大小(包含节点 i),

而在 rson 的子树里,我们依旧可以保留下 dp[rson] 个节点,所以最终的状态转移方程就是:

dp[i]=max(dp[lson]+size[rson]−1,dp[rson]+size[lson]−1)

需要注意可能子树为空,那么size[son]−1 就是一个负数,所以需要对 0 取 max⁡。

为避免这种情况,我们要先进行 dfs 再更新 size 和 dp 数组。

以及多组数据所以每次需要将各种数组清零,清零 head 数组也相当于清零边,小小地卡一点常数。

因为是二叉树,那么一个点被感染后,接下来去除的点一定是其左右儿子 sonl​ 和 sonr​ 之一。当去除一个儿子之后,以该儿子为根的子树中的点(除了去除的儿子)全部存活,另一个儿子接下来被感染,问题转化为了被感染的儿子的那棵子树能够存活多少,成了原问题的子问题。那么,就能得到:fi​=max(fson[l]​+sizeson[r]​−1,fson[r]​+sizeson[l]​−1),其中 size 存子树大小,要减 1 是因为我们去除的那个点不算存活。

#include
#include
#include
#include
#include
#define int long long
#define WR WinterRain
using namespace std;
const int WR=1001000,mod=1e9+7;
struct Edge{
    int pre,to;
}edge[WR];
int t;
int n,sze[WR];
int head[WR],tot;
int dp[WR];
int read(){
    int s=0,w=1;
    char ch=getchar();
    while(ch>'9'||ch<'0'){
        if(ch=='-') w=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9'){
        s=(s<<3)+(s<<1)+ch-48;
        ch=getchar();
    }
    return s*w;
}
void add(int u,int v){
    edge[++tot].pre=head[u];
    edge[tot].to=v;
    head[u]=tot;
}
void dfs(int u,int root){
    sze[u]=1,dp[u]=0;//dp数组存储如果我被感染了能活多少个
    int sum=0;
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v!=root){
            dfs(v,u);
            sum+=dp[v];
            sze[u]+=sze[v];
        }
    }
    for(int i=head[u];i;i=edge[i].pre){
        int v=edge[i].to;
        if(v!=root){
            dp[u]=max(dp[u],sum-dp[v]+sze[v]-1);
        }
    }
}
signed main(){
    t=read();
    while(t--){
        n=read();
        tot=0;
        for(int i=1;i<=n;i++) head[i]=0;
        for(int i=1;i

你可能感兴趣的:(深度优先,算法,图论)