比赛记录:Codeforces Round 872 (Div. 2) A~D2

传送门:CF

本场因为C题赛时被题意绕晕了,导致狠狠的掉分(准确的来说是题意没有想错但是自己的脑子被绕晕了)

A题:A. LuoTianyi and the Palindrome String

考虑暴力进行循环,找到所有所有不同的字符对(不妨记为 [ l , r ] [l,r] [l,r]).那么对于这个字符区间来说,显然中间的所有字符都是可以选的(因为左右两端点已经是不同的了).并且因为这个字符对的存在,我们可以在这个字符串的基础上向左右延伸扩展.
对于每一个字符对的最大贡献取一个 m a x max max即可.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int main() {
	int T=read();
	while(T--) {
		string s;cin>>s;
		int ans=0;int l=0,r=0;
		for(int i=0;i<s.length();i++) {
			for(int j=s.length()-1;j>i;j--) {
				if(s[i]!=s[j]) {
					l=i;r=j;
					ans=max(ans,2*min(l,(int)s.length()-r-1)+r-l+1);
				}
			}
			if(ans==1) break;
		}
		if(ans==0) cout<<-1<<endl;
		else {
			cout<<ans<<endl;
		}
	}
	return 0;
}

B题:B. LuoTianyi and the Table

不难发现应该将最小的那个值使用的最多,然后最大的值也使用的最多是最优的.
我们把玩一下题意之后就会发现 ( 1 , 1 ) (1,1) (1,1)的这个位置显然应该使用最大的数字(这对吗?不对.还可能使用最小的数字),所以此时就有两种情况了,因为篇幅原因,此处我只描述一下放最大值的情况.最小值的分析方法与之几乎相同
假设我们在(1,1)出放最大值.我们会发现 ( 1 , 2 ) , ( 2 , 1 ) (1,2),(2,1) (1,2),(2,1)的位置显然应该放最小值( m i n 1 min1 min1)和较小值( m i n 2 min2 min2).我们会发现显然只要最小值存在,那么我们的值就是 m a x − m i n 1 max-min1 maxmin1,不然肯定就是 m a x − m i n 2 max-min2 maxmin2.因为我们需要最大,所以我们需要后者的数量更小.诶此时我们会发现后者的数字和该行(或者该列)的个数有关.也就是后者的个数要么是 n − 1 n-1 n1,要么是 m − 1 m-1 m1.所以此时当 n < m n<m n<m时放在(2,1),反之放(1,2)即可.
所有的子矩阵的个数为n*m, ( 1 , 1 ) (1,1) (1,1)位置自己占了一个位置, m a x − m i n 2 max-min2 maxmin2占了 n − 1 n-1 n1或者 m − 1 m-1 m1个矩阵,剩下来的都是 m a x − m i n 1 max-min1 maxmin1.所以此时本题也就不难解决了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define int long long
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int b[maxn];
signed main() {
	int T=read();
	while(T--) {
		int n=read(),m=read();
		int maxx=-int_INF;
		for(int i=1;i<=n*m;i++) {
			b[i]=read();
		}
		sort(b+1,b+n*m+1);
		maxx=b[n*m];
		int minn1=b[1],minn2=b[2];
		int Min=min(n,m);
		cout<<max((n*m-Min)*(maxx-minn1)+(maxx-minn2)*(Min-1),
			(n*m-Min)*(maxx-minn1)+(b[n*m-1]-minn1)*(Min-1))<<endl;
	}
	return 0;
}

C题:C. LuoTianyi and the Show

就是这道题,出题风格只能说很 c n cn cn好吧,狠狠的让我把题意理解错了.
首先这道题的题意是我们可以任意的进行顺序安排,问存在一种可能的全坐下的最大人数,然后刚开始我当做"存不存在一种最大人数使得任意的排序都可以坐下".然后狠狠的被卡了.(当然这个错误题意也是可以做的,可以造出来当做新生赛的一道题了,就先这么定了)
还有就是这道题的安排方式是只能安排在左端,右端.当我在后半时期领悟到我之前看错题目之后然后又忘记了这个字.导致又狠狠的 w a wa wa了,最离谱的是还能过样例.


接下来讲一下这道题的解法吧.看明白题意之后那么这道题就很简单了.发现先放 − 1 , − 2 -1,-2 1,2和先放固定座位的人的情况分析是不一样的.

  1. 假设我们先放 − 1 -1 1,此时我们因为刚开始没有人,所以此时我们放在了最右端,此时最右端就已经被放满了,所以此时我们就会发现无论如何 − 2 -2 2都没办法放了.但是我们会发现此时我们可以将剩下的所有 − 1 -1 1都插空到所有固定座位的人之间.这是为什么呢,因为我们此时的 − 1 -1 1可以一直放,然后遇到第一个有固定座位的人,然后让这个固定座位的人坐下,然后继续在这个人的左边继续放 − 1 -1 1.但是需要注意的是存不存在固定位置在m的人,这个需要特判一下
  2. 假设我们先放 − 2 -2 2,此时的情况和 − 1 -1 1是基本一样的.我们可以将 − 2 -2 2放在所有的固定位置的人的位置之间,这样子是最优的
  3. 假设我们先放固定位置的人.那么此时我们会发现我们的 − 1 -1 1只能放在这个位置的左边了, − 2 -2 2只能放在这个位置的右边了.那么我们此时最优的方案就是先选第一个位置,然后在这个位置的左右不断的填补 − 1 , − 2 -1,-2 1,2,直到遇到下一个固定的位置,然后填上那一个位置,然后继续填补 − 1 , − 2 -1,-2 1,2.所以只要考虑枚举第一个固定的位置即可.

可能会有人会有一个小疑问,为什么我们遇到固定的位置一定要先填上固定的位置呢,不能继续填 − 1 , − 2 -1,-2 1,2吗.现在来解决一下这个问题:
考虑到我们的填数的方向肯定是一个方向的(不妨假设在pos的位置我们开始往一个方向开始填数,不妨设填的数字为 − 1 -1 1,也就是一直往左填),那么显然我们会发现用之前的填数方法 − 1 -1 1是可以填在任意的左边位置的,那么我们可以填在固定的位置上的 − 1 -1 1为什么不填在其他没有数的位置呢.因为我们的 − 1 -1 1肯定是可以填在其他空着的位置上的.我们却将这个 − 1 -1 1占用了其他数的位置.这显然是不优的.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int a[maxn];
map<int,int>mp;
int main() {
	int T=read();
	while(T--) {
		int n=read(),m=read();mp.clear();
		for(int i=1;i<=n;i++) {
			a[i]=read();
		}
		vector<int>v;int v2=0,v3=0;
		for(int i=1;i<=n;i++) {
			if(mp[a[i]]&&a[i]>0) continue;
			if(a[i]==-1) v2++;
			else if(a[i]==-2) v3++; 
			else v.push_back(a[i]);
			mp[a[i]]=1;
		}
		if(v.size()==0) {
			cout<<min(max(v2,v3),m)<<endl;
			continue;
		}
		int ans=0;
		sort(v.begin(),v.end());
		for(int i=1;i<v.size();i++) {
			ans+=v[i]-v[i-1]-1;
		}
		int ans1=min(m,v2+(int)v.size()-(v.back()==m?1:0));
		int ans2=min(m,v3+(int)v.size()-(v[0]==1?1:0));
		int ans3=-int_INF;
		for(int i=0;i<v.size();i++) {
			int pos=i+1;
			ans3=max(ans3,min(v[i]-(pos-1)-1,v2)+min(m-v[i]-((int)v.size()-pos),v3)+(int)v.size());
		}
		cout<<max(ans1,max(ans2,ans3))<<endl;
	}
	return 0;
}

D题:D1. LuoTianyi and the Floating Islands (Easy Version&Hard Version)

在此处我直接讲Hard Version了.代码也只给出D2的实现代码.
首先我们会发现,显然当 k = 1 k=1 k=1的时候,我们每一次选择都只能得到1的贡献,所以最后的答案肯定是1
k = 3 k=3 k=3时候,我们会发现诶,好像无论任何情况也只有1的贡献.合理猜想,当 k = 2 n + 1 k=2n+1 k=2n+1的时候,我们每一次的选择都只有1的贡献.下面来简单证明一下:
考虑现在有k个点(k为奇数).随机分布一下.那么显然此时我们至少可以找到一个点到所有点的距离和最小(记为g),原因是因为距离和必定存在一个最小值.那么所有k个点 a 1 , a 2... a k a1,a2...ak a1,a2...ak必然通过一些边和这个g相连记为 e 1 , e 2 , e 3... e p e1,e2,e3...ep e1,e2,e3...ep.那么假设此时我们还存在一个点使得距离和最小,那么我们的这个点肯定是需要往这些边的方向进行移动的.假设现在往 E E E这条边的另一端进行移动.那么对于这条边来说,边的两端的点的个数肯定是不一样的.我们往一段移动必然会减少边的一端的距离和,增加另一端的距离和.并且此时的总距离和肯定是增加的,不然就与我们之前的最小值假设矛盾了.并且如果此时增加的话,说明如果我们继续向这个方向走一定是一直增加的,因为边的一段的点会越来越多.
所以我们得到一个结论就是当 k & 1 k\&1 k&1时必然答案是1.
下面分析k为偶数的时候.
对于任意一种随机情况,我们假设此时的 u u u是一个好节点.那么对于这个情况来说,我们此时的好节点的个数就等于(1+u能往周围移动的点的个数)所以我们此时的问题就变成了如何计算u能移动多少次(因为每移动一次必然会增加一个点)

直接考虑比较困难,发现每次转移必然会通过一条边,所以我们考虑计算每一条边为这个移动所做出的贡献.我们发现只要一条边的两端随机分布的点的个数一样(也就等于k/2),那么这条边必然可以产生一个贡献.因为这条边的两个端点必然是好节点(因为每条链上的分布点的个数都小于k/2,所以无论往哪边移动,都不会减少距离和,并且在一直相等之后必然是增加的)[注意这个结论很重要].这个结论说明了只要这种边存在就能且必然会提供一个贡献.
反之我们可以证明无法扩散.为了保证方案数不重不漏,使用dfs进行遍历即可
最终的答案就是所有边能提供的贡献/总方案数+1.这个1是因为每一种方案必然会产生一个好节点.
所以本题代码也就不难给出了

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define int long long
const int mod=1e9+7;
const double eps=1e-8;
#define	int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
vector<int>edge[maxn];
int Size1[maxn];
ll qpow(ll a,ll b) {
	ll ans=1;
	while(b) {
		if(b&1) ans=(ans*a)%mod;
		b>>=1;
		a=(a*a)%mod;
	}
	return ans;
}
void dfs1(int u,int per_u) { 
	Size1[u]=1;
	for(int i=0;i<edge[u].size();i++) {
		int v=edge[u][i];
		if(v==per_u) continue;
		dfs1(v,u);
		Size1[u]+=Size1[v];
		
	}
}
int ans=0;int n,k;
int fac[maxn],in_fac[maxn];
void init() {
	fac[0]=in_fac[0]=1;
	for(int i=1;i<=n;i++) {
		fac[i]=fac[i-1]*i%mod;
		in_fac[i]=in_fac[i-1]*qpow(i,mod-2)%mod;
	}
}
int C(int a,int b) {
	if(a>=b) return fac[a]*in_fac[b]%mod*in_fac[a-b]%mod;
	else return 0;
}
void dfs3(int u,int per_u) {
	int sum=0;
	for(int i=0;i<edge[u].size();i++) {
		int v=edge[u][i];
		if(v==per_u) continue;
		ans=(ans+C(Size1[v],k/2)*C(n-Size1[v],k/2)%mod)%mod;
		dfs3(v,u);
	}
}
signed main() {
	n=read();k=read();
	init();
	for(int i=1;i<=n-1;i++) {
		int u=read(),v=read();
		edge[u].push_back(v);
		edge[v].push_back(u);
	}
	if(k&1) {
		cout<<1<<endl;
		return 0;
	}
	dfs1(1,0);dfs3(1,0);	
	int sum=C(n,k);
	cout<<(ans*qpow(sum,mod-2)%mod+1)%mod<<endl;
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值