1.Problem - 1739C - Codeforces
这道题不是很好理解,给出的操作很多。
题意简述:给你一个树的结构,让你给这个树赋点权(1∼n 的排列)。
然后每次删掉一个没有儿子(或儿子都被删去)的点,将这个点的点权加入答案序列中;如果该点父亲的点权小于该点的点权,则用该点的点权更新父亲点权。
对于每个节点所在的子树有两种情况:
对这两种情况取一个 maxmax,做树形 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 都会在开局分得牌堆中的 张牌,手牌互异,点数随机。 首先玩家 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