noip8

T1

星际旅行
考试时觉得是道数学题,但没想到忘了欧拉路。

首先将每条边都拆成两条边,那么题目就变成了任意删掉两条边,使得新的图中存在欧拉路。设 \(sum\) 表示自环的数量, \(du_{i}\) 表示不算自环,点 \(i\) 的度数,考虑以下几种情况,

  • 删任意两个自环,则对答案的贡献为 \(\frac{sum\times\left(sum-1\right)}{2}\)

  • 删一个自环和任意一条边,则对答案的贡献为 \(sum\times(m-sum)\)

  • 删两条有公共顶点的边,因为删完边后,图中点的度数都为偶数,所以删的边肯定是要有一个公共顶点的,则对答案的贡献为 \(\sum_{i=1}^{n}\frac{du_{i}\times\left(du_{i}-1\right)}{2}\)

注意判断图是否连通。

Code
#include<cstdio>
#define MAX 100001
#define re register
#define int long long
namespace OMA
{
   int n,m;
   struct Graph
   {
     int next;
     int to;
   }edge[MAX<<1];
   int cnt=1,head[MAX];
   int ans,sum,du[MAX];
   int vis[MAX],link[MAX];
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline void add(int u,int v)
   {
     edge[++cnt].next = head[u];
     edge[cnt].to = v;
     head[u] = cnt;
   }
   inline void dfs(int u)
   {
     vis[u] = 1;
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(!vis[v])
       { dfs(v); }
     }
   }
   signed main()
   {
     n = read(),m = read();
     for(re int i=1; i<=m; i++)
     {
       int u = read(),v = read();
       if(u==v)
       { link[u] = 1,sum++; }
       else
       {
         add(u,v),add(v,u);
         du[u]++,du[v]++;
       }
     }
     for(re int i=1; i<=n; i++)
     {
       if(link[i]||du[i])
       { dfs(i); break; }
     }
     for(re int i=1; i<=n; i++)
     {
       if(!vis[i]&&(link[i]||du[i]))
       { printf("0\n"); return 0; }
     }
     ans = sum*(m-sum)+sum*(sum-1)/2;
     for(re int i=1; i<=n; i++)
     { ans += du[i]*(du[i]-1)/2; }
     printf("%lld\n",ans);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T2

考试时想的是二分,但二分并不正确,因为并不满足单调性好像是屁话
首先我们能得出一个式子,

\[\sum_{i=1}^{n}\left(\left\lceil\frac{a_{i}}{d}\right\rceil\times d-a_{i}\right) \le k \]

移项可得,

\[\sum_{i=1}^{n}\left\lceil\frac{a_{i}}{d}\right\rceil\le \frac{k+\sum_{i=1}^{n}a_{i}}{d} \]

设右侧的一大堆为 \(temp\) ,然后我们枚举 \(d\) ,用分块的思想,显然,对于每一个 \(temp\) 相等的区间中,右端点是最优的,然后判断是否合法,然后转移答案。

Code
#include<cmath>
#include<cstdio>
#include<algorithm>
#define MAX 101
#define re register
#define int long long
namespace OMA
{
   int a[MAX];
   int n,k,ans;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   signed main()
   {
     n = read(),k = read();
     for(re int i=1; i<=n; i++)
     { k += a[i] = read(); }
     for(re int l=1,r; l<=k; l=r+1)
     {
       r = k/(k/l);
       int temp = 0;
       for(re int i=1; i<=n; i++)
       { temp += (int)ceil((double)a[i]/r); }
       if(temp<=k/r)
       { ans = r; }
     }
     printf("%lld\n",ans);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T3

一开始以为是个数学题,开始打表找规律,无果,想了想,觉得是个 \(dp\) 然而我状态转移方程啥也推不出来。输出0骗了5pts

正解是个 \(dp\) ,设 \(dp_{i,j}\) 表示是 \(i\)-超级树,有 \(j\) 条点不重复的路径的方案数。

考虑\(dp_{i}\)\(dp_{i+1}\) 的贡献,枚举左子树的路径数 \(l\) ,右子树的路径数 \(r\) ,设 \(temp=dp_{i,l}\times dp_{i,r}\) ,有以下几种情况,

  • 啥也不干, \(dp\left(i+1,l+r\right)+=temp\)
  • 根自己作为一条新路径, \(dp\left(i+1,l+r+1\right)+=temp\)
  • 根连接到左子树或右子树的一条路径上, \(dp\left(i+1,l+r\right)+=2\times temp\times \left(l+r\right)\)
  • 根连接左子树和右子树的一条路径, \(dp\left(i+1,l+r-1\right)+=2\times temp\times l\times r\)
  • 根连接左子树或右子树的两条路径, \(dp\left(i+1,l+r-1\right)+=temp\times\left(l\times\left(l-1\right)+r\times\left(r-1\right)\right)\)

然后直接转移即可。

Code
#include<cstdio>
#define MAX 301
#define re register
#define int long long
namespace OMA
{
   int k,mod;
   int dp[MAX][MAX];
   signed main()
   {
     scanf("%lld%lld",&k,&mod);
     dp[1][0] = dp[1][1] = 1;
     for(re int i=0; i<=k-1; i++)
     {
       for(re int l=0; l<=k; l++)
       {
         for(re int r=0; r<=k; r++)
         {
           int temp = dp[i][l]*dp[i][r]%mod;
           if(l+r<=k)
           {
             dp[i+1][l+r] += temp;
             dp[i+1][l+r] += 2*temp*(l+r);
             dp[i+1][l+r] %= mod;
           }
           if(l+r+1<=k)
           { (dp[i+1][l+r+1] += temp) %= mod; }
           if(l+r-1<=k)
           {
             dp[i+1][l+r-1] += 2*temp*l*r;
             dp[i+1][l+r-1] += temp*((l*(l-1))+r*(r-1));
             dp[i+1][l+r-1] %= mod;
           }
         }
       }
     }
     printf("%lld\n",dp[k][1]%mod);
     return 0;
   }
}
signed main()
{ return OMA::main(); }

T4

求和
本次考试最水的一道题,然而 \(LCA\) 打错了,直接爆0。

直接找询问的两个点的 \(LCA\) 然后从 \(LCA\) 开始分别向两个点枚举深度,并统计答案,记得最后再容斥一下,因为 \(LCA\) 的深度算了两遍。

然后这样过不掉luogu的加强数据,只需预处理一下前缀和即可。
以下为朴素算法

Code
#include<cstdio>
#define MAX 300001
#define re register
#define int long long
namespace OMA
{
   int n,m;
   struct Graph
   {
     int next;
     int to;
   }edge[MAX<<1];
   int bin[MAX];
   int cnt=1,head[MAX];
   int anc[MAX][50],dep[MAX];
   const int p = 998244353;
   inline int read()
   {
     int s=0,w=1; char ch=getchar();
     while(ch<'0'||ch>'9'){ if(ch=='-')w=-1; ch=getchar(); }
     while(ch>='0'&&ch<='9'){ s=s*10+ch-'0'; ch=getchar(); }
     return s*w;
   }
   inline void add(int u,int v)
   {
     edge[++cnt].next = head[u];
     edge[cnt].to = v;
     head[u] = cnt;
   }
   void dfs(int u,int fa)
   {
     dep[u] = dep[anc[u][0] = fa]+1;
     for(re int i=1; i<=bin[dep[u]]; i++)
     { anc[u][i] = anc[anc[u][i-1]][i-1]; }
     for(re int i=head[u]; i; i=edge[i].next)
     {
       int v = edge[i].to;
       if(v!=fa)
       { dfs(v,u); }
     }
   }
   inline void swap(int &a,int &b)
   { int t=a; a=b; b=t; }
   inline int LCA(int a,int b)
   {
     if(dep[a]<dep[b])
     { swap(a,b); }
     while(dep[a]>dep[b])
     { a = anc[a][bin[dep[a]-dep[b]]]; }
     if(a==b)
     { return a; }
     for(re int i=bin[dep[a]]; ~i; i--)
     {
       if(anc[a][i]!=anc[b][i])
       { a = anc[a][i],b = anc[b][i]; }
     }
     return anc[a][0];
   }
   inline int quickpow(int a,int b)
   {
     int ans = 1;
     while(b)
     {
       if(b&1)
       { ans = ans*a%p; }
       a = a*a%p;
       b >>= 1;
     }
     return ans;
   }
   signed main()
   {
     n = read();
     for(re int i=2; i<=n; i++)
     {
       int u = read(),v = read();
       add(u,v),add(v,u);
       bin[i] = bin[i>>1]+1;
     }
     dfs(1,0);
     m = read();
     for(re int i=1; i<=m; i++)
     {
       int a = read(),b = read(),k = read();
       int lca = LCA(a,b),ans = 0;
       for(re int j=dep[lca]; j<=dep[a]; j++)
       { ans = (ans+quickpow(j-1,k))%p; }
       for(re int j=dep[lca]; j<=dep[b]; j++)
       { ans = (ans+quickpow(j-1,k))%p; }
       ans = (ans-quickpow(dep[lca]-1,k))%p;
       printf("%lld\n",(ans%p+p)%p);
     }
     return 0;
   }
}
signed main()
{ return OMA::main(); }
posted @ 2021-06-19 11:22  -OMA-  阅读(113)  评论(0编辑  收藏  举报
浏览器标题切换
浏览器标题切换end