Codeforces Round #409 (Div. 2) ABCDE

本文详细解析了五道ACM竞赛题目,包括字符串处理、二分查找、几何问题等,提供了多种算法解决方案,并附带完整的代码实现。

作为一个咸鱼自从上了紫之后就再也不敢打cf了 orz

A题:

直接枚举修改点然后每次修改扫一次统计一次VK个数取最大值。O(n^2)

虽然隐约觉得有O(n)的做法不过为了手速还是可耻的写了纯暴力。

代码:

#include<bits/stdc++.h>
using namespace std;

int check(string s)
{
	int ret=0;
	for(int i=0;i<s.length()-1;i++)
		if(s[i]=='V'&&s[i+1]=='K')ret++;
	return ret;
}

int main()
{
	string s;
	while(cin>>s)
	{
		int ans=check(s);
		for(int i=0;i<s.length();i++)
		{
			if(s[i]=='V')
			{
				s[i]='K';
				ans=max(ans,check(s));
				s[i]='V';
			}
			if(s[i]=='K')
			{
				s[i]='V';
				ans=max(ans,check(s));
				s[i]='K';
			}
		}
		cout<<ans<<endl;
	}
}

方法2:时间复杂度O(n),因为你最多只能改变一次,所以只需要先把全部的VK标记一下,然后第二次扫的时候看看如果存在连续两个位置都没有被标记并且两个的字符相同,那么这个位置是可以变成VK的,答案+1就好了。代码如下:

#include<iostream>  
#include<cmath>  
#include<queue>  
#include<cstdio>  
#include<queue>  
#include<algorithm>  
#include<cstring>  
#include<string>  
#include<utility>
#include<map>
#include<vector>
#define maxn 105
#define inf 0x3f3f3f3f  
using namespace std;
typedef long long LL;
const double eps = 1e-8;
char s[maxn];
int vis[maxn];
int main(){
	scanf("%s", s);
	int ans = 0;
	for (int i = 0; s[i+1] != '\0'; i++){
		if (s[i] == 'V'&&s[i + 1] == 'K'){
			vis[i] = vis[i + 1] = 1;
			ans++;
			i++;
		}
	}
	for (int i = 0; s[i+1] != '\0'; i++){
		if (!vis[i] && !vis[i + 1] && s[i] == s[i + 1]){
			ans++;
			break;
		}
	}
	printf("%d\n", ans);
}


B题:

首先O(n)扫一遍判断第一个字符串中有没有对应位置字符比第二个串小的。

有的话直接输出-1,没有的话直接输出第二个串。

代码:

#include<bits/stdc++.h>
using namespace std;

string a,b;

int main()
{
	while(cin>>a>>b)
	{
		string ret;
		bool flag=true;
		for(int i=0;flag&&i<a.length();i++)
		{
			if(a[i]<b[i])flag=false;
		}
		if(flag)cout<<b<<endl;
		else cout<<"-1"<<endl;
	}
}

C题:

二分答案,精度取到1e-5应该就够了。然后就变成判断性问题了。

假设当前二分的时间为t,扫一遍所有的设备,求出其要运作到时间t所需要的充电时间。然后求个和再跟t比较,如果比t大就说明不可能。

注意设备的充电时间不能是负数,以及当一台设备就算是全程充电也达不到t时间的时候直接false。

坑点是二分的初始上下界和精度,调不好的话不是WA就是TLE。

代码:

#include<bits/stdc++.h>
using namespace std;

struct node
{
	double a,b;
};

node a[100005];
int n;
double p;

bool check(double m)
{
	double t=0;
	for(int i=0;i<n;i++)
	{
		double t0=a[i].a*m-a[i].b;
		if(t0>m*p)return false;
		if(t0>0)t+=t0;
	}
	return t<=m*p;
}

int main()
{
	while(scanf("%d%lf",&n,&p)==2)
	{
		for(int i=0;i<n;i++)
			scanf("%lf%lf",&a[i].a,&a[i].b);
		double l=0,r=2e10;
		while(r-l>(4*1e-5))
		{
	//		printf("%.14lf %.14lf\n",l,r);
			double mid=(l+r)/2;
			if(check(mid))l=mid;
			else r=mid;
		}
		if(r==2e10)printf("-1\n");
		else printf("%.10lf\n",l);
	}
}

D题:

枚举凸多边形上所有的相邻三点,设这三个按照顺时针顺序依次为p0,p1,p2。

点的移动最大距离这个问题我们可以看做是以点为圆心,最大距离为半径作圆。则点的移动范围就是在这个圆内了。

那么只需要保证p1一直在p0和p2连的线段上方,以及p0与p2的移动范围不相交就行了。

也就是说,首先圆的半径必须小于等于distance(p0,p2),其次必须小于等于distance(p1,线段p0p2)/2。

那么对于当前枚举的三个点来说,最大半径r=min(distance(p0,p2),distance(p1,线段p0p2)/2)。

枚举完取最小值即可。

代码:

#include<bits/stdc++.h>
using namespace std;

struct Point
{
	double x,y;
	Point(double xx=0,double yy=0):x(xx),y(yy){}
	double operator -(const Point &p)const
	{
		return sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y));
	}
};

double Dis(Point a,Point b,Point c)
{
	return fabs((c.y-a.y)*b.x+(a.x-c.x)*b.y+(c.x*a.y-a.x*c.y))/(a-c);
}

double Caclu(Point a,Point b,Point c)
{
	double ret=Dis(a,b,c)/2;
	ret=min(ret,(a-c)/2);
	return ret;
}

Point pt[1005];

int main()
{
	int n;
	while(scanf("%d",&n)==1)
	{
		for(int i=0;i<n;i++)
			scanf("%lf%lf",&pt[i].x,&pt[i].y);
		double ans=Caclu(pt[0],pt[1],pt[2]);
		for(int i=1;i<n;i++)
			ans=min(ans,Caclu(pt[i],pt[(i+1)%n],pt[(i+2)%n]));
		printf("%.10lf\n",ans);
	}
}
E题:

先统计所有的不在输入序列且小于m的正整数与m的gcd并归类。

这一步可以不用算gcd而是用类似于素数筛的方法完成。先将在输入序列的数标记为已访问,然后从m-1扫到1,如果没有被访问过且能整除m的话(设当前枚举的数为i),就将它以及它的倍数(小于m且未访问)均添加进一个序列中,这个序列的所有数与m的gcd均为i。姑且将这个序列标记为v[i]吧。

然后,dfs出m的约数的整除偏序关系的哈斯图,不过边的方向需要修改。(例如说m=24,那么建成的图就是1->2 1->3 2->4 2->6 3->6 4->8 4->12 6->12)。

这个只需要从1开始搜索,对所有当前点乘以一个素数的点连边即可。保证点的值小于m就行了。

最后,给这个图的每个点一个权值,点i的权值就是v[i]的长度。然后从点1开始树形dp一遍求最大的权值和的链即可。

那么这个最大权值和就是待构造的数列的长度。而数列的前缀积的结果便是搜索过的点对应的v[i]从左往右拼接起来。

上面的建图跟树形dp可以一起做一次dfs。

最后就是根据前缀积还原出原序列。假设当前前缀积为a,下一个前缀积为b,当前位置的元素为x。那么现在要求x,其实就是求ax≡b(mod m)这条线性同余方程的解。

由于搜索路径是按与m的gcd增加上去的,故保证对于每个线性同余方程都有解。

最后要对0做特殊处理。如果输入的序列没有0的话,答案的序列需要push_back一个0。

思路有点复杂,我想应该不是官方正解的思路不过。。。

能A就行了。

代码:

#include<bits/stdc++.h>
using namespace std;

bool ss[200005]={0};
vector<long long> prime;

void init()
{
	for(int i=2;i<=200000;i++)
	{
		if(!ss[i])
		{
			prime.push_back(i);
			for(int j=2;j*i<=200000;j++)
				ss[j*i]=true;
		}
	}
}

bool vis[200005]={0};
long long n,m;

vector<long long> ans;

vector<long long> v[200005];

long long nxt[200005]={0};

long long dp[200005];
void dfs(long long pos)
{
	if(dp[pos]!=-1)return;
	dp[pos]=v[pos].size();
	long long pp=0;
	for(int i=0;i<prime.size();i++)
	{
		if(pos*prime[i]>=m)break;
		dfs(pos*prime[i]);
		if(nxt[pos]==0||pp<dp[pos*prime[i]])
		{
			nxt[pos]=pos*prime[i];
			pp=dp[pos*prime[i]];
		}
	}
	dp[pos]+=pp;
}

struct tri
{
	long long d,x,y;
	tri(long long dd=0,long long xx=0,long long yy=0):d(dd),x(xx),y(yy){}
};

tri exgcd(long long a,long long b,long long c)
{
	if(b==0)return tri(c,c/a,0);
	tri p=exgcd(b,a%b,c);
	return tri(p.d,p.y,p.x-(a/b)*p.y);
}

long long caclu(long long a,long long b)
{
	tri p=exgcd(a,m,b);
	p.x=(p.x%m+m)%m;
	return p.x;
}

void solve()
{
	for(int i=m-1;i>0;i--)
	{
		if(m%i==0)
		{
			for(int j=i;j<m;j+=i)
			{
				if(!vis[j])
				{
					v[i].push_back(j);
					vis[j]=true;
				}
			}
		}
	}
	memset(dp,-1,sizeof(dp));
	dfs(1);
	long long pos=1;
	long long p=1;
	while(pos!=0)
	{
		for(int i=0;i<v[pos].size();i++)
		{
			ans.push_back(caclu(p,v[pos][i]));
			p=v[pos][i];
		}
		pos=nxt[pos];
	}
	if(!vis[0])ans.push_back(0);
}

int main()
{
	init();
	scanf("%lld%lld",&n,&m);
	for(int i=0;i<n;i++)
	{
		long long p;
		scanf("%lld",&p);
		vis[p]=true;
	}
	solve();
	printf("%lu\n",ans.size());
	for(int i=0;i<ans.size();i++)
	{
		if(i)printf(" ");
		printf("%lld",ans[i]);
	}
	printf("\n");
}


总结:嗯 感觉C应该是最难的。二分的上下界跟精度坑的贼惨。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值