今年应该就是最后一年的比赛了,毕竟大三狗了。学了快三年,博客这种东西写一篇少一篇。 退役前可能还能自我训练几次,不多了。。。。
http://blog.csdn.net/column/details/codeforces-dp.html?&page=2
多谢黎晨dalao,学习您的这个博客是我受益匪浅。
简单dp:
codeforces 494B B. Obsessive String
http://codeforces.com/contest/494/problem/B
这题还挺难的。。。
给一个串s 和 模式串t
求合法集合的多少;
合法集合: 有一个过多个s的子串构成, 子串不能重叠,子串必须包含模式穿t。
比如ababa: =5 t=aba
{aba}{abab}{ababa}{baba}{aba}
那如果 abaaba t=aba
{aba}{abaa}{abaab}{abaaba}{baaba}{aaba}{aba}
以及:
{aba aba}
我们试着解释一下第三组样例:
d1d2d3
d
=12
d3:{d3} dp[3]=1
d2:{d2} {d2d3} + {d2 + d3} dp[2]=2 + 1 = 3
d1:{d1} {d1d2} {d1d2d3} + {d1+d2} {d1+d2d3} {d1+ d2 +d3 } {d1+d3} +{d1d2 +d3}
dp[3] = 3 + 3*1 + 1*2=8
=12
如果照这么理解: dp[i] = dp[i+2]*2 为什么呢?
因为 我们已 {模式串t1 + dp[i+2]} 也可以 {模式串t1 i+1 + dp[i+2] }
同理如果是i+3 可以 {i + dp[i+3]} { ii+1 +dp[i+3] } {ii+1i+2 + dp[i+3]} 所以应该*3
首先我们要能够确定在 s串中t出现的位置。
设 dp[i] :以i 开头的往后的有多少个
所以
如果 i 不是匹配模式串 的开头
dp[i]=dp[i+1]; 相当于把当前i 加给后面 dp[i+1]代表的 合法集合
如果是:
dp[i]=n-(i+len2-1)+
dp[i+len2] //集合: 第一个模式串 + 以i+len2 …开头往后的合法集合
dp[i+len2+1]*2 // : 第一个模式串 + 以i+len2+1 开头的合法集合数量
const ll mod=1e9+7;
char pattern[200005],text[200005]; //pattern 模式串 ,text是主串,nexxt是处理之后的跳转 都是从0开始保存!
__int64 nexxt[200005];
int matches[200005];
__int64 ans;
int len1,len2;
void BuildNext() //KMP算法中所用到的跳转表next,是算法的核心问题
{
len2=strlen(pattern);
int i, t;
i = 1;
t = 0;
nexxt[1] = 0;
while(i < len2+1 ){ // t-表示-f(j)
while(t > 0 && pattern[i - 1] != pattern[t - 1]){
t = nexxt[t];
}
++t;
++i;
if(pattern[i - 1] == pattern[t - 1]){
nexxt[i] = nexxt[t];
}
else{
nexxt[i] = t;
}
}
//pattern末尾的结束符控制,用于寻找目标字符串中的所有匹配结果用
while(t > 0 && pattern[i - 1] != pattern[t - 1]){
t = nexxt[t];
}
++t;
++i;
nexxt[i] = t;
}
__int64 KMP()
{
int i, j;
__int64 ans;
BuildNext();
len1=strlen(text);
i = 0;
j = 1;
ans = 0;
while(len2 +1 - j <= len1 - i){
// printf("%d %d :%c %c\n",i,j-1,text[i],pattern[j - 1]);
if(text[i] == pattern[j - 1] ){
++i;
++j;
//发现匹配结果,将匹配子串的位置,加入结果
if(j == len2+1 ){
matches[ans++] = i-len2;
j = nexxt[j];
}
}
else{
j = nexxt[j];
if(j == 0){
++i;
++j;
}
}
}
//返回发现的匹配数
return ans;
}
int flag[200005];
ll dp[200005];
ll sum[200005];
ll sum_dp[200005];
// sum[i] 应该 = i + i+1 * 2 + i+2 *3 + i+3 * 4.... sum[i+1]= i+1 + i+2 *2 + ...
// sum[i] = dp[i] + sum[i+1] + dp[i+1] + dp[i+2] +......;
int main(){
// freopen("1.txt","r",stdin);
scanf("%s",text);
scanf("%s",pattern);
int L=KMP();
memset(flag,0,sizeof(flag));
for(int i=0;i// printf("mat=%d\n",matches[i]);
flag[matches[i]]=1;
}
ll ans=0;
dp[len1]=0;
sum[len1]=0;
sum_dp[len1]=0;
for(int i=len1-1;i>=0;i--){
if(flag[i]==0){
dp[i]=dp[i+1];
}
else{
dp[i]= (len1-1) - (i+ len2 -1) +1;
dp[i]+= sum[i+len2] ; // i+len2 + i+len2+1 *2 + i
dp[i]%=mod;
}
sum[i]=(dp[i]+ sum[i+1]+sum_dp[i+1])%mod;
sum_dp[i]=(sum_dp[i+1] + dp[i])%mod;
ans=(ans+dp[i])%mod;
// printf("i=%d sum=%I64d %I64d %I64d %I64d \n",i,sum_dp[i],sum[i],dp[i],ans);
}
printf("%I64d\n",ans);
return 0;
}
codeforces 571B B. Minimization
给一个数组a 3*1e5 和一个数k <5000
可以随便改变这些元素的顺序,也可以不改变
使得 abs(a[1]-a[1+k]) + abs(a[2]-a[2+k]) …+ abs(a[n-k]-a[n]) 最小
整个数组可以看成
[..k..] [.k..] [..k..] [..n%k..]
= [..n%k] [..k..] [..k..] [..k..]
= [ .n%k.+ k-n%k..] [..k..] [..k..] [.n%k..]
考虑长度为 n/k + 1的链 有 n%k 条
那么剩下的长度为 n/k的链 有 k - n%k条
{两种已经分完了所有的数。仔细读读题意就可以发现了}
对于某条链,我们从小到大排 且是连续的数构成, 最小的差值就是max-min
我们分成两种链 就好了
其中长度为 n/k + 1的链 有 n%k 条,长度为 n/k的链 有 k - n%k条
我们不确定的是: 按什么样的顺序分配给 链1 链2 可以获得最小值,那么我们就可以开始dp了,于是看起来变成了简单dp。
我们设dp[i][j] 表示第一种链为i第二种链为j 的最小值
dp[i][j] = min dp[i-1][j] + a[ i*l1+j*l2 ]-a[ i*l1+j*l2-l1 +1 ]
dp[i][j-1] + a[ i*l1+j*l2 ]-a[ i*l1+j*l2-l2 +1 ]
#define ll __int64
ll dp[5005][5005];
int a[300005];
int main(){
// freopen("1.txt","r",stdin);
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
int l1=n%k;
int l2=k-n%k;
int len1=n/k+1;
int len2=n/k;
memset(dp,63,sizeof(dp)); // 这样可以直接复制最大值?? 学到了
dp[0][0]=0;
for(int i=0;i<=l1;i++) //
for(int j=0;j<=l2;j++){
// printf("%I64d\n",dp[i][j]);
int endd=i*len1+j*len2;
if(i>0) dp[i][j]=min(dp[i][j],dp[i-1][j] + a[endd]-a[endd-len1+1]);
if(j>0) dp[i][j]=min(dp[i][j],dp[i][j-1] + a[endd]-a[endd-len2+1]);
}
printf("%I64d\n",dp[l1][l2]);
return 0;
}
codeforces 583B B. Once Again…
http://codeforces.com/contest/583/problem/D
给一个数组a
n*T
i>n ai=ai-n=a[i%n]
找到最长 非递减数列
分析:
XJB想了一下午,其实最开始的想法就是对的
暴力跑出 n*n的 LIS
然后把 t-n 插入LIS中就行了。
#define ll __int64
int dp[100005];
int a[100005];
int b[100005];
int main(){
// freopen("1.txt","r",stdin);
int n,t;
map<int,int>mp;
int maxn=0;
scanf("%d %d",&n,&t);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
mp[a[i]]++;
maxn=max(maxn,mp[a[i]]);
}
int L=0;
int temp=t;
if(temp>n){
temp=n;
}
for(int j=1;j<=temp;j++)
for(int i=1;i<=n;i++)
b[++L]=a[i];
int len=1;
dp[1]=b[1];
for(int i=2;i<=L;i++){
int pos=upper_bound(dp+1,dp+1+len ,b[i])-dp;
if(pos>len)
dp[++len]=b[i];
else
dp[pos]=b[i];
}
if(t>n)
len+=(t-n)*maxn;
printf("%d\n",len);
return 0;
}
codeforces 158E. Phone Talks(dp)
http://codeforces.com/contest/158/problem/E
有n个电话 要拨打
每个电话有:开始的时间ti , 和持续的时长di
所有的ti不相同,
可以忽视掉 最多K 个电话,可以推到第二天甚至以后
问:当天最多可以连续睡多少时间 [1,86400]
忽略掉的K 个电话肯定是连续的
感觉 不用dp啊,n*n 能跑过去吧
还是 naive了,有时候不能去掉连续的。。。因为持续时间的不同
比如
[1,10] [2,5][30,1] [100,100]; 去掉两个 去掉 2 3 和1 2 就不如去掉1 3
那么我们就只能想想dp了
设 dp[i][j] 假设前i个电话里面 推 k-j个电话 还剩j个的最长休息时间。
我们会发现方程无法写出,因为无法知道dp[i-1] 里面到底推掉了那些电话。
设:dp[i][j]表示 接完第i个电话且推掉j个电话 的 最少需要的时间。
dp[i][j] =
if(dp[i-1][j] > f[i].sta) dp[i][j]= dp[i-1][j]+f[i].d;
else dp[i][j] = f[i].end;
dp[i][j]=min(dp[i][j], dp[i-1][j-1]);
我们知道了完成前i个电话,推掉j个的最少时间
那么我们就可以
for(int i=1;)..
for(int j=0;j….)
推掉 dp[i][j] 前i个电话里面的j个最快完成时间
然后把 i 后面连续的 k-j 的电话都推掉,求出时间间隔。
似乎是正确的
struct node{
int sta,d,to;
bool operator < (node b)const{
return sta4005];
int dp[4005][4005];
int main() {
int n,k;
scanf("%d %d",&n,&k);
for(int i=1;i<=n;i++){
scanf("%d %d",&f[i].sta,&f[i].d);
f[i].to=f[i].sta+f[i].d;
}
if(k==n){
printf("86400\n");
return 0;
}
sort(f+1,f+n+1);
dp[0][0]=1;
dp[0][1]=1;
dp[1][0]=f[1].to;
for(int i=1;i<=n;i++){
for(int j=0;j<=k;j++){
if(j==i){
dp[i][j]=1;
break;
}
if(j>=i-1 || dp[i-1][j]<=f[i].sta)
dp[i][j]=f[i].to;
else if(dp[i-1][j] > f[i].sta)
dp[i][j]= dp[i-1][j]+f[i].d;
if(j>0)
dp[i][j]=min(dp[i][j], dp[i-1][j-1]);
}
}
int ans=0;
for(int i=1;i<=n;i++){ // 假设i前面
for(int j=0;j<=k;j++){ // 前i个推掉j 个,假设前i+(k-j) 个推掉了k个,即吧i 后面的连续的全部推掉
ans=max(ans,f[i+1].sta-dp[i][j]);
if(i+k-j+1>n)
ans=max(ans,86401-dp[i][j]);
else
ans=max(ans,f[i+k-j+1].sta-dp[i][j]);
}
}
printf("%d\n",ans);
return 0;
}
codeforces 163A Substring and Subsequence(dp)
http://codeforces.com/contest/163/problem/A
x 是第一个字符串的 子串 len<5000
y 是第二个字符串的 子序列 len<5000
问x=y的 有多少对 允许n*n的时间复杂度
设 dp[i][j] 为第一个字符串前i个子串 和第二 的前j个子序列 相同的有多少个
if i==j
那么dp[i][j] =
dp[i-1][j-1] :
1….i-1 i
1….j-1 j
aaa
aaa
dp[1][1]=dp[0][0]+1=1; dp[1][2]=dp[1][1]+1=2 ; dp[1][3]=dp[1][2]+1=3;
dp[2][1]=dp[1][1]+1=2 dp[2][2]=dp[1][1]+dp[1][2]+dp[2][1]-dp[1][1] +1 =5
dp[2][3] = dp[1][3] + dp[2][2] +1 = 8+1=9 (-dp[1][1] +dp[1][1])
dp[3][1]=dp[2][1]+1=3;
dp[3][2]=8 = dp[3][1]+ dp[2][2] +1 - dp[3-2][1]=8; 因为 1 -3 必须是连续的子串
dp[3][3]=16= dp[3][2]+dp[2][3]+1 =8+9+1 - dp[1][2]=16;
好像 对于i,j
有dp[i][j]= dp[i][j-1] + dp[i-1][j] +1 - (dp[i-2][j-1]) ;
因为括号部分是重复部分,而题目要求 a串中选出的必须是子串,所以1
const ll mod=1e9+7;
char s1[5005];
char s2[5005];
ll dp[5005][5005];
int main() {
// freopen("1.txt","r",stdin);
int n,k;
scanf("%s %s",s1+1,s2+1);
int l1=strlen(s1+1);
int l2=strlen(s2+1);
memset(dp,0,sizeof(dp));
for(int i=1;i<=l1;i++)
for(int j=1;j<=l2;j++){
if(s1[i]==s2[j]){
if(i>2)
dp[i][j]= ( (dp[i][j-1] + dp[i-1][j])%mod + mod - dp[i-2][j-1])%mod;
else
dp[i][j]= dp[i][j-1] + dp[i-1][j] ;
dp[i][j]++;
}
else
dp[i][j]=( (dp[i-1][j] +dp[i][j-1])%mod + mod -dp[i-1][j-1])%mod;
dp[i][j]%=mod;
// printf("%d %d =%I64d\n",i,j,dp[i][j]);
}
printf("%I64d\n",dp[l1][l2]);
return 0;
}
codeforces 180C C. Letter(dp)
区间dp:
codeforces 245H H. Queries for Number of Palindromes(区间dp)
概率dp:
codeforces 351B B. Jeff and Furik(概率)
树形dp:
codeforces 486D D. Valid Sets ;http://codeforces.com/contest/486/problem/D
题意:
给一颗树,每个点上有值ai
找到 多少个 连通子树 且 其中最大值-最小值<=d
从时间复杂度上看 我们可以采用n*n
也就是说可以把每一个点当成根,扫一遍,得出所有 !包含root! 的满足条件的子树个数。
我们对于每个根,把它当成最大值,遇到比他大的我们就中断
只考虑比它小的子树,而且必须满足a[root]-a[i]<=d,否则也中断
那么考虑 u->v
dp[v]=x 那么dp[u]:代表 从root - u 都满足条件数量=dp[u],那么
dp[v]的含义 本来就是从 包含root-v的前提下,v 下面的子树可以构成 x 个满足条件的,
那么dp[u] 的新增的数量 就是dp[u]*dp[v];
这样还是会有重复值
比如 3-5 和 5-3 可能就产生重复值,所以我们规定只能从3-5 或者只能从5-3 就好了,这种解法真是机智啊。
int a[N];
vector<int >G[N];
ll dp[N];
int n,d;
void dfs(int u,int pre,int rot){
for(int i=0;iint v=G[u][i];
if(v==pre) continue;
if(a[v]>a[rot]) continue;
if(a[rot]-a[v]>d) continue;
if(a[rot]==a[v] && v>rot) continue; //定义只能从 rot->v 即rot
//满足了 所有条件之后,说明仅v 贡献值为1
dp[v]=1;
dfs(v,u,rot);
// printf("i=%d u-v %d %d %I64d\n",i,u,v,dp[v]);
dp[u]+=dp[u]*dp[v];
dp[u]=dp[u]%mod;
}
}
int main(){
//freopen("1.txt","r",stdin);
scanf("%d %d",&d,&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int a,b;
for(int i=1;iscanf ("%d %d",&a,&b);
G[a].push_back(b);
G[b].push_back(a);
}
ll ans=0;
for(int i=1;i<=n;i++){
dp[i]=1;
dfs(i,0,i);
ans+=dp[i];
ans=ans%mod;
}
printf("%I64d\n",ans);
return 0;
}
codeforces 337D D. Book of Evil(树形dp)
题意:给一棵树
两个点的距离= 经过的边的条数
有一个污染源,能污染距离<=d的点。
现给你一些被污染的点。 其他点不知是否被污染了。
问:污染源 有可能出现在几个地点。
n,m=1e5
所以n*n的方法被否决。
有一个基本的想法: dfs一遍,把所有污染源能够污染的点+1
On扫一遍得到所有污染数=m的点。
因为如果dfs,那么有些点能够影响祖先、兄弟。。。。显然难以处理,需要把每个点当成根来做。 O(n*m)
然后我们可以用dp 来做一做(别问为什么,看的题解)
dp[i]代表某个点到 离它最远的确定的感染点 的距离。
因为树:
我们可以知道每个点i 到 他的子树中的感染点的最远距离
问题2:如何得到每个点 除了自己、自己的子树之外的 最远感染点距离。
我们可以知道每个点的 所有儿子节点的 son[u]
那么 son[1] ..son[2].. son[i+1]….
对于1 来说fa[1]=max(son[2]….son[x]) if(存在) + 1;
//那么其实只要维护最大值,和次大值即可
using namespace std;
#define ll __int64
/*
5 4 1
2 3 4 5
1 2
2 3
2 4
1 5
*/
#define N 100005
struct node{
int to,d;
int next;
}edge[N*2];
int head[N],tot;
void addedge(int u,int v){
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
int p[N];
int son[N],fa[N];
int flag[N];
void init(){
memset(flag,0,sizeof(flag));
memset(head,-1,sizeof(head));
tot=0;
memset(son,-31,sizeof(son));
memset(fa,-1,sizeof(fa));
}
void dfs(int u,int pre,int l){ // 得到子树中最大的距离
// printf("u=%d p=%d\n",u,pre);
if(flag[u]==1)
son[u]=0;
for(int i=head[u];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to==pre) continue;
// printf("u=%d %d\n",u,to);
dfs(to,u,l+1);
son[u]=max(son[u],son[to]+1);
}
}
void dfs2(int u,int pre){
if(flag[pre]==1) //这里显然是错的,肯定不能直接覆盖
fa[u]=max(fa[u],1);
if(fa[pre]>-1)
fa[u]=max(fa[u],fa[pre]+1);
// printf("dfs2 :%d %d %d \n",u,pre,fa[u]);
int maxn=-1,minn=-1;
int p1;
for(int i=head[u];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to==pre) continue;
// printf("u to :%d %d %d\n",u,to,son[to]);
// 维护方法有问题啊。这样会覆盖掉次大值,比如 2 .3 先进入2 覆盖maxn,然后3也会覆盖maxn
if(son[to] > minn){ //肯定要先和次大值比,在分情况讨论
if(son[to]>maxn){
minn=maxn;
maxn=son[to];
p1=to;
}
else{
minn=son[to];
}
}
}
// if(u==1)
// printf(" %d %d %d\n",maxn,minn,p1);
for(int i=head[u];i!=-1;i=edge[i].next){
int to=edge[i].to;
if(to==pre) continue;
if(fa[u]>-1)
fa[to]=max(fa[to],fa[u]+1);
if(to==p1){
if(minn>-1)
fa[to]=max(fa[to],minn+2);
}
else{
if(maxn>-1)
fa[to]=max(fa[to],maxn+2);
}
// if(u==1)
// printf("u=fa[v]= %d %d %d \n",u,to,fa[to]);
dfs2(to,u);
}
}
int main() {
int n,m,d;
init();
scanf("%d %d %d",&n,&m,&d);
for(int i=1;i<=m;i++){
scanf("%d",&p[i] );
flag[p[i]]=1;
}
int u,v;
for(int i=1;i"%d%d",&u,&v);
addedge(u,v);
addedge(v,u);
}
dfs(1,0,0);
dfs2(1,0);
int ans=0;
for(int i=1;i<=n;i++){
// printf("%d %d %d\n",i,son[i],fa[i]);
if(son[i]>d || fa[i]>d)
ans++;
}
printf("%d\n",n-ans);
return 0;
}
codeforces 461B B. Appleman and Tree(树形dp)
http://codeforces.com/contest/461/problem/B
给一棵树,n个定点,每个点非黑即白
问有多少种方法将去掉一些边 k条
之后形成k+1 个连通块,使得每个连通块里面仅有1个黑点
首先我们很容易能想到一种方法:把每个黑点和父亲的边删去。
用树形dp 的思想:
设:
dp[i][2]
dp[i][0]表示i包含i的子树 连通块无黑点的方案数,1表示有黑点的方案书
所以如果一个点是黑色dp[i][1]=1
如果一个点是白色dp[i][0]=1
我们考虑,在已知根的情况下,添加子树
1.如果当前根i 有黑点了:
添加的子树树根有黑点: 这条边只能分割; 结果: dp[i][1] * dp[v][1]
添加的子树没有黑点: 这条边只能保留 结果: dp[i][1]*dp[v][0]
2.如果当前根i 没有黑点:
添加的子树有黑点 :
这条边保留:dp[i][0] * dp[v][1]
这条边分割:dp[i][1] * dp[v][1]
添加的子树无黑点
这条边只能保留:dp[i][0] * dp[v][0]
说实话我觉得dp的转移有点莫名其妙就过了,正确性真是。。。
using namespace std;
#define ll __int64
/*
*/
const ll mod=1e9+7;
#define N 100005
struct node{
int to,d;
int next;
}edge[N*2];
int head[N],tot;
int col[N];
void addedge(int u,int v){
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}
ll dp[N][2];
void init(){
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
memset(col,0,sizeof(col));
tot=0;
}
void dfs(int u,int pre){
dp[u][col[u]]=1;
// printf("u= %d %d %d col=%d\n",u,dp[u][0],dp[u][1],col[u]);
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].to;
if(v==pre) continue;
dfs(v,u);
dp[u][1]=( (dp[u][1] *dp[v][0])%mod + (dp[u][0]*dp[v][1])%mod + (dp[u][1]*dp[v][1])%mod )%mod;
dp[u][0]=( (dp[u][0] *dp[v][0])%mod + (dp[u][0]*dp[v][1])%mod )%mod; // 改成这个莫名过:dp[v][1]
// printf("u= %d %d %d v=%d %d %d\n",u,dp[u][0],dp[u][1],v,dp[v][0],dp[v][1]);
}
}
int main() {
//freopen("1.txt","r",stdin);
int n;
init();
scanf("%d",&n);
int x;
for(int i=1;i
scanf("%d",&x);
addedge(i,x);
addedge(x,i);
}
for(int i=0;i
scanf("%d",&col[i]);
}
dfs(0,-1);
printf("%I64d\n",dp[0][1]);
return 0;
}
codeforces 219D D. Choosing Capital for Treeland(树形dp)
http://codeforces.com/contest/219/problem/D
n个点 n-1条边 道路是单向路
选一个点,能从这个点到达其他所有点。
如果选择 一点为首都,那么所有路都要变为有向。因此有些路需要被反转
问选哪个(些)点,被反转的路最少。
输入反转路的数量
和选择哪些点。
我们考虑 u->v
假设 dp[v]:从v 开始 往下的子树中的最小反转次数,那么这个很好解决
那么:我们必须知道 v 往上的最小反转次数
而这个似乎是不能找出来的, 能想到的方法就是将这个点转换为根
那么如果我们 知道dp[root] 代表root 到所有点的最小反转次数
那对于root(u)->v
up[v] = dp[u] + u-v是否需要反转.
这是成立的,虽然看起来似乎不可思议。
明显dp[root]就是到所有点的最小次数
我们可以理解为 我们如果以v 为根,那么我们只需要把 u->v 改为v->u + v->v的子树
其他地方就按原来的更改即可。
如果uv的方向是正向的, dp[u]没有更改,但是
如果方向为 v-u , 那么dp[u]中肯定改成u-v 我们不需要更改这条边。
const ll mod=1e9+7;
#define N 200005
vector<int>G[N];
int dp[N]; // i以及 子树 i都可达的最小的反转次数
map<int,map<int,int> >mp;
void add(int u,int v){
G[u].push_back(v);
G[v].push_back(u);
}
void dfs(int u,int pre){
dp[u]=0;
for(int i=0;iint to=G[u][i];
if(to==pre) continue;
int flag=1;
if(mp[u][to]==1)
flag=0;
dfs(to,u);
dp[u]+=dp[to]+flag;
// printf("u= %d %d %d\n",u,to,dp[u]);
}
}
void dfs2(int u,int pre){
for(int i=0;iint to=G[u][i];
if(to==pre) continue;
int flag=1;
if(mp[u][to]==0) //
flag=-1;
dp[to] =dp[u] + flag; // ->dp:i 可达整棵树
dfs2(to,u);
// printf("u= %d %d %d %d\n",u,to,dp[u],dp[to]);
}
}
int main() {
//freopen("1.txt","r",stdin);
int n;
scanf("%d",&n);
int u,v;
mp.clear();
for(int i=1;i<=n;i++)
G[i].clear();
for(int i=1;iscanf("%d %d",&u,&v);
add(u,v);
mp[u][v]=1;
}
dfs(1,0);
dfs2(1,0);
int ans=1<<30;
for ( int i = 1 ; i <= n ; i++ )
ans = min ( ans , dp[i] );
printf ( "%d\n" , ans );
for(int i=1;i<=n;i++)
if(dp[i]==ans){
printf("%d ",i);
}
printf("\n");
return 0;
}
状压dp: