2 条题解

  • 1
    @ 2023-11-10 16:19:07

    题目描述

    Z 国的骑士团是一个很有势力的组织,帮会中汇聚了来自各地的精英。他们劫富济贫,惩恶扬善,受到社会各界的赞扬。

    最近发生了一件可怕的事情,邪恶的 Y 国发动了一场针对 Z 国的侵略战争。战火绵延五百里,在和平环境中安逸了数百年的 Z 国又怎能抵挡的住 Y 国的军队。于是人们把所有的希望都寄托在了骑士团的身上,就像期待有一个真龙天子的降生,带领正义打败邪恶。

    骑士团是肯定具有打败邪恶势力的能力的,但是骑士们互相之间往往有一些矛盾。每个骑士都有且仅有一个自己最厌恶的骑士(当然不是他自己),他是绝对不会与自己最厌恶的人一同出征的。

    战火绵延,人民生灵涂炭,组织起一个骑士军团加入战斗刻不容缓!国王交给了你一个艰巨的任务,从所有的骑士中选出一个骑士军团,使得军团内没有矛盾的两人(不存在一个骑士与他最痛恨的人一同被选入骑士军团的情况),并且,使得这支骑士军团最具有战斗力。

    为了描述战斗力,我们将骑士按照 1 至 n 编号,给每名骑士一个战斗力的估计,一个军团的战斗力为所有骑士的战斗力总和。


    解法

    这道题的图一定是若干个不连通的基环树,考虑到一条边的限制是两边不能同时选,我们先将一个边的限制确定了,再对整棵树跑没有上司的舞会,所以我们就取环上的一对点,跑两次d p dpdp,每次限制某一个点一定不能选,这道题细节有点多,详细看代码。


    题解

    #include <cstdio>//by hetao5487227
    #include <iostream>
    using namespace std;
    #define int long long
    #define inf 0x3f3f3f3f3f3f3f3f
    const int MAXN = 1000005;
    int read()
    {
    	int x=0,flag=1;char c;
    	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return x*flag;
    }
    int n,x,y,tot,ans,f[MAXN],pd[MAXN],vis[MAXN];
    int vis2[MAXN],val[MAXN],dp[MAXN][2];//0/1 选/不选 
    struct edge
    {
    	int v,next;
    }e[MAXN*2];
    void find(int u,int fa)//找环 
    {
    	vis[u]=pd[u]=1;//vis判断是否访问,pd找环只找反祖边(卡掉二元环) 
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(v==fa) continue;
    		if(pd[v])//找到了返祖边,有环 
    		{
    			x=u;y=v;
    			continue;
    		}
    		if(vis[v]) continue;//只能向没有标记的地方跑 
    		find(v,u);
    		pd[v]=0;//只能找返祖边,所以清零 
    	}
    }
    void dfs(int u,int fa)
    {
        vis2[u]=1;//判断访问过没有 
    	if(dp[u][0]!=-inf) dp[u][0]=val[u]; 
    	dp[u][1]=0;//赋初值 
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(vis2[v] || (u==x && v==y) || (u==y && v==x)) continue;
    		//只能走没访问过的点,不能走返祖边 
    		dfs(v,u);
    		dp[u][0]+=dp[v][1];//没有上司的舞会 
    		dp[u][1]+=max(dp[v][1],dp[v][0]);
    	}
    }
    void clear(int u,int fa)//清空vis2 
    {
    	vis2[u]=0;
    	for(int i=f[u];i;i=e[i].next)
    	{
    		int v=e[i].v;
    		if(vis2[v]) clear(v,u);
    	}
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<=n;i++)
    	{
    		val[i]=read();int j=read();
    		e[++tot]=edge{j,f[i]},f[i]=tot;
    		e[++tot]=edge{i,f[j]},f[j]=tot;
    	}
    	for(int i=1;i<=n;i++)
    		if(!vis[i])
    		{
    			int Max=0;
    			x=y=0;
    			find(i,0);
    			dp[x][0]=-inf;//限制x不能选 
    			dfs(i,0);
    			clear(i,0);//清空vis2 
    			Max=max(Max,max(dp[i][0],dp[i][1]));
    			dp[x][0]=0;//清除标记 
    			dp[y][0]=-inf;//限制y不能选 
    			dfs(i,0);
    			Max=max(Max,max(dp[i][0],dp[i][1]));
    			dp[y][0]=0;
    			ans+=Max;//每个基环树的最大值求和 
    		}
    	printf("%lld\n",ans);
    }
    
    • 0
      @ 2023-7-5 1:16:24

      认真读完题目我们会发现

      因为一个骑士有且只有一个最讨厌的人

      而且这个骑士不会讨厌自己

      即该图中是没有自环的

      所以 考虑这个图

      我们把x所讨厌的人y设为x的父亲节点

      这样考虑每一个人都有且只有一条出边

      所以对一个"联通块"

      只有根节点有机会形成环

      即环一定包含根节点

      为什么呢?

      因为一个点的出度只能为1

      考虑非根节点

      它的出度一定是贡献给它的父亲的

      而根节点它的出度只能贡献给它的后代

      (这里的"根节点""叶子节点"都只是为了描述方便,并不严谨,也许可以理解为"删边以后的叶子和根"?)

      所以我们又解决了一个问题:

      每个联通块内有且只有一个简单环

      这样 我们考虑把每个联通块的环上删一条边

      这样它必然构成树

      然后要注意

      删掉的边所连接的两点x,y

      是不能同时选的

      所以我们分别强制x,y其中一个点不选

      对新树跑DP

      显然相邻的点是不能选的

      所以得到状态转移方程:

      用f[i][0/1]表示以i为根节点的子树选i/不选i所能获得的最大价值

      则 f[i][0]=sigema(max(f[son][0],f[son][1])); for each son of i

      f[i][1]=sigema(f[son][0]); for each son of i

      应该就很清楚了

      再一个细节就是答案会爆int

      代码:

      #include<iostream>
      #include<cstdio>
      #include<cstring>
      #define maxn 2000000
      using namespace std;
      int n,cnt;
      long long ans;
      int root;
      long long f[maxn][2];
      int head[maxn],val[maxn],vis[maxn],fa[maxn];
      struct edge
      {
          int pre,to;
      }e[maxn];
      inline void add(int from,int to)
      {
          e[++cnt].pre=head[from];
          e[cnt].to=to;
          head[from]=cnt;
      }
      void dp(int now)
      {
          vis[now]=1;
          f[now][0]=0,f[now][1]=val[now];
          for(int i=head[now];i;i=e[i].pre)
          {
              int go=e[i].to;
              if(go!=root)
              {
                  dp(go);
                  f[now][0]+=max(f[go][1],f[go][0]);
                  f[now][1]+=f[go][0];
              }
              else
              {
                  f[go][1]=-maxn;
              }
          }
      }
      inline void find_circle(int x)
      {
          vis[x]=1;
          root=x;
          while(!vis[fa[root]])
          {
              root=fa[root];
              vis[root]=1;
      

      }//找环

      dp(root);
          long long t=max(f[root][0],f[root][1]);
          vis[root]=1;
          root=fa[root]; 
          dp(root);
          ans+=max(t,max(f[root][0],f[root][1]));
          return;
      }
      inline int in()
      {
          char a=getchar();
          while(a<'0'||a>'9')
          {
              a=getchar();
          }
          int t=0;
          while(a<='9'&&a>='0')
          {
              t=(t<<1)+(t<<3)+a-'0';
              a=getchar();
          }
          return t;
      }
      //写一下伪代码 
      //在存的时候存一下每个人最讨厌的人为他的父亲
      //对每个没访问的点DFS 
      //在这个没访问的点所在的连通块上找环
      //找到以后强制不选它的父亲对它进行DP
      //然后强制不选它对它的父亲进行DP
      //然后取一个最大值即可 
      //在DP里面
      //先考虑f[x][0]是不选x,初始值为0
      //f[x][1]是选x,初值为val[x]
      //这是一个很好的赋值方法
      //然后跑DP就行了 
      int main()
      {
          n=in();
          for(int i=1;i<=n;i++)
          {
              val[i]=in();
              int x=in();
              add(x,i);
          //    add(i,x);
              fa[i]=x;
          }
          for(int i=1;i<=n;i++)
          {
              if(!vis[i])
              {
                  find_circle(i);
              }
          }
          printf("%lld\n",ans);
          return 0;
      }
      
      • 1

      信息

      ID
      242
      时间
      1000ms
      内存
      125MiB
      难度
      8
      标签
      递交数
      25
      已通过
      6
      上传者