题目地址:https://codeforces.com/contest/1336
A Linova and Kingdom
题意:一棵树根结点为 1,要选出 k 个结点,使得这 k 个结点到根结点的路径中非选中结点的总数最大。
思路:考虑贪心的思路,如果只选 1 个,那么肯定选择深度最深的最优,如果选很多个,那么某个结点肯定没有它的儿子更优,所以如果我们选了一个结点,那么它的儿子一定全都被选了。所以考虑选择某个结点 u 的时候,它对答案的贡献为 d[u]-(size[u]-1),其中 d 为深度,size 为子树大小。按照贡献排序,从大到小选即可。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=2e5+5;
vector<int> G[maxn];
int n,k,d[maxn],s[maxn],a[maxn];
void dfs(int u,int fa,int de)
{
d[u]=de; s[u]=1;
for(int v:G[u]) if(v!=fa)
{
dfs(v,u,de+1);
s[u]+=s[v];
}
}
int main()
{
//freopen("input.txt","r",stdin);
n=read(); k=read();
REP(i,1,n-1)
{
int u=read(),v=read();
G[u].push_back(v); G[v].push_back(u);
}
dfs(1,0,0);
REP(i,1,n) a[i]=d[i]-s[i]+1;
sort(a+1,a+n+1,greater<int>());
LL ans=0;
REP(i,1,k) ans+=a[i];
cout<<ans;
return 0;
}
B Xenia and Colorful Gems
题意:有三个数组,要求从三个数组中各选一个数 x, y, z 出来,使得 ( x − y ) 2 + ( y − z ) 2 + ( z − x ) 2 (x-y)^2+(y-z)^2+(z-x)^2 (x−y)2+(y−z)2+(z−x)2 的值最小,并求出这个最小值。
思路:一个朴素的想法就是,遍历其中一个数组,对于这个数组中的 x,我们从另外两个数组中分别二分选出两个与 x 最接近的数(一个大于一个小于),然后更新答案。当然不能只遍历一个数组,三个都要。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=1e5+5;
LL r[maxn],g[maxn],b[maxn];
int nr,ng,nb;
LL cal(int i,int j,int k)
{
LL x=(r[i]-g[j])*(r[i]-g[j]);
LL y=(r[i]-b[k])*(r[i]-b[k]);
LL z=(g[j]-b[k])*(g[j]-b[k]);
return x+y+z;
}
LL solve()
{
LL ret=3e18;
REP(i,1,nr)
{
int g1=min(lower_bound(g+1,g+ng+1,r[i])-g,ng);
int g2=max(g1-1,1);
int b1=min(lower_bound(b+1,b+nb+1,r[i])-b,nb);
int b2=max(b1-1,1);
ret=min(ret,cal(i,g1,b1));
ret=min(ret,cal(i,g1,b2));
ret=min(ret,cal(i,g2,b1));
ret=min(ret,cal(i,g2,b2));
}
return ret;
}
int main()
{
//freopen("input.txt","r",stdin);
int T=read();
while(T--)
{
nr=read(),ng=read(),nb=read();
LL ans=3e18;
REP(i,1,nr) r[i]=read();
REP(i,1,ng) g[i]=read();
REP(i,1,nb) b[i]=read();
sort(r+1,r+nr+1);
sort(g+1,g+ng+1);
sort(b+1,b+nb+1);
REP(i,1,3)
{
ans=min(ans,solve());
swap(r,g); swap(g,b);
swap(nr,ng); swap(ng,nb);
}
printf("%lld\n",ans);
}
return 0;
}
C Kaavi and Magic Spell
题意:有两个字符串 S 和 T,我们遍历 S 来构造 A,每遇到一个字符,可以放在 A 的前面,也可以放在 A 的后面。问有多少种构造 A 的方式,使得最后 A 的前缀是 T 。
思路:很明显的dp(然而状态式一直设不对)。设 f ( l , r ) f(l,r) f(l,r) 表示从 S 已经选择了 r-l+1 个字符,并且 A[l…r] 与 T[l…r] 匹配的方案数,这里要注意对于 A 中比 T 长的那些位置,一定是匹配的,然后就是很简单的转移。
代码:
#include <bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;
int read()
{
int x=0,flag=1;
char c=getchar();
while((c>'9' || c<'0') && c!='-') c=getchar();
if(c=='-') flag=0,c=getchar();
while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return flag?x:-x;
}
const int maxn=3005;
const int M=998244353;
int n,m,f[maxn][maxn];
char s[maxn],t[maxn];
int main()
{
//freopen("input.txt","r",stdin);
scanf("%s %s",s+1,t+1);
n=strlen(s+1); m=strlen(t+1);
REP(i,1,n+1) f[i][i-1]=1;
for(int i=1;i<=n;i++)
for(int l=1;l+i-1<=n;l++)
{
int r=l+i-1;
if(l>m || s[i]==t[l]) f[l][r]+=f[l+1][r];
if(r>m || s[i]==t[r]) f[l][r]+=f[l][r-1];
f[l][r]%=M;
}
int ans=0;
REP(r,m,n) ans=(ans+f[1][r])%M;
cout<<ans;
return 0;
}
D
题意:
思路:
代码:
E
题意:
思路:
代码:
F
题意:
思路:
代码: