codeforces---748

D题

题意:给出k个长度为n的字符串,每个串有一个价值vi,现在请用这k个串(不必全用完)构成一个长的回文串,使这个串价值最大。

做法:先注意到一点:他们的长度是相等的,所以其实说是回文但实际并不需要什么后缀数组的,因为一个串在回文的另一端必定只对应另一个完整的串。贪心,先处理出来每个串是否回文,若否,它的对应串是谁(这里我们需要用map来编号),并把这个串的所有值存到它的优先队列里。

先对于所有不是回文的进行一番贪心:若它和它的对应串的最大值之和>0,那么就把这个值加到ans里,代表我选了它们作为总串的一部分。然后pop掉并继续这个操作直到两者的最大值<=0;

.对于是回文的,操作与上者差不多,改成了自己和自己贪心,但注意,它们也可以选一个作为中心串。所以维护一个值mx表示选一个串当作中心串所能获得的最大优化利益(初始化肯定为0).在和自己贪心的过程中,若优先队列里只剩一个值,那么mx=max(mx,q.top())。若贪心加的两个值中的较小值<0,那么也要与这个值的相反数取一下最大值(即把较大值作为中心串时获得的优化利益),还要注意若较大值次大值<=0时需要注意,若x1>0,优化利益也需要和它做一个max,再break。

最后输出ans+mx即可。

#include<iostream>
#include<string.h>
#include<algorithm>
#include<math.h>
#include<stdio.h>
#include<map>
#include<vector>
#include<queue>
using namespace std;
const int N=100050;
string s,rank[N];
vector<int> a,b;
map<string,int> ma;  
int cnt,n,k,v;
struct st
{
	priority_queue<int,vector<int>,less<int> > q;
	int dui;
}gh[N];
int ans=0,bu=0;
int main()
{
	scanf("%d%d",&k,&n);
	for(int i=1;i<=k;i++)
	{
		cin>>s;
		cin>>v;
		if(!ma[s])
			ma[s]=++cnt,rank[cnt]=s;
		gh[ma[s]].q.push(v);	
	}
	for(int i=1;i<=cnt;i++)
	{
		string now=rank[i],rev;
		for(int j=n-1;j>=0;j--)
			rev+=now[j];
		int to=ma[rev];
		gh[i].dui=to;
		if(to==i)
			a.push_back(i);
		else if(to)
			b.push_back(i);
	}
	for(int i=0;i<b.size();i++)
	{
		int to=gh[b[i]].dui;
		if(gh[b[i]].q.empty()||gh[to].q.empty())
			continue;
		while(gh[b[i]].q.top()+gh[to].q.top()>0)
		{
			ans+=gh[b[i]].q.top()+gh[to].q.top();
			gh[b[i]].q.pop(),gh[to].q.pop();
			if(gh[b[i]].q.empty()||gh[to].q.empty())
			break;
		}
	}
	for(int i=0;i<a.size();i++)
	{
		int now=a[i];
		if(gh[now].q.size()==1)
		{
			bu=max(bu,gh[now].q.top());
			continue;
		}
		int x1=gh[now].q.top();gh[now].q.pop();
		int x2=gh[now].q.top();gh[now].q.pop();
		while(x1+x2>0)
		{
			ans+=x1+x2;
			if(x2<0)
				bu=max(bu,-x2);
			if(gh[now].q.empty())
				break;
			if(gh[now].q.size()==1)
			{
				bu=max(bu,gh[now].q.top());
				break;
			}
			x1=gh[now].q.top(),gh[now].q.pop(),x2=gh[now].q.top(),gh[now].q.pop();
		}
		if(x1+x2<=0)
			bu=max(bu,x1);
	}
	cout<<ans+bu<<endl;
}

E题

题意:一个圣诞老人有一堆橘子,每个有vi瓣,现在给k个熊孩子分橘子。每次圣诞老人因为手残只能把一个橘子或一个整块橘子瓣们分成vi/2和vi-vi/2两半,而熊孩子们也只要整块的橘子(即一个完整的橘子或一块切出来的橘子瓣们),最终圣诞老人获得的快乐值为所有人中获得橘子瓣数最少的那个,问圣诞老人所能获得的最大快乐值是多少。

做法:最大的最小值!二分!

然而。。其实并不需要二分。。。。

cnt[i]表示瓣数为i的橘子有多少个,tot表示现在最小的是i时能切出多少块。

那么每到一个瓣数时,tot+=cnt[i],tot-=cnt[2*i],tot-=cnt[2*i-1]表示加上这个块数,但它是由2*i和2*i-1转移过来的,所以要把它们的个数减去.

有人(开始的在下。。)会不服:那为什么不减cnt[2*i+1]?1.若两个都减,那么一个瓣数的值会被所切成的两部分各减一次,那么就减多啦!2.如果只减cnt[2*i+1]而非cnt[2*i],以9为例,它会在i=4时被减,那么先前在i=5时tot就大了。

当tot>=k时,输出i.

#include<iostream>
#include<string.h>
#include<algorithm>
#include<stdio.h>
#include<math.h>
#include<queue>
using namespace std;
queue<int> q;
int n,mx,a[1000050],k;
long long cnt[10000050],sum;
long long tot;
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),sum+=a[i],mx=max(mx,a[i]),cnt[a[i]]++;
	if(sum<k)
	{
		printf("-1");
		return 0;
	}
	for(int i=mx;i>=1;i--)
	{
		tot+=cnt[i];
		if(1<i*2&&i*2<=mx)
			tot-=cnt[i*2];
		if(1<i*2-1&&i*2-1<=mx)
			tot-=cnt[i*2-1];
//		if(1<i*2+1&&i*2+1<=mx)
//			tot-=cnt[i*2+1];
		if(tot>=k)
		{
			printf("%d\n",i);
			return 0;
		}
		cnt[i/2]+=cnt[i],cnt[i-i/2]+=cnt[i];
	}
}

F题(第一道凭自己能力做出来的F题,感动!)

题意:给出一幅地图,n个城市成树状相连,现在给出2*k个队伍,他们来自不同的城市,现在比赛期间他们要组成k对比赛对手,对于每对比赛对手,他们在比赛期间所住的城市一定是他们俩各自城市最短路上的一个城市(可以是这两个端点城市)。现在委员会想让所有队伍所住城市的总数最小,问最小是多少,并且他们都在那,还有此时每对对手由谁组成,以及他们住哪一个。

做法:结论:只需要一个城市。理由:只要一个城市T它的儿子节点所含队伍数不超过k个,那么这个城市便可以作为所有队伍所住的城市,因为可以给任意一个队伍找一个不在同一棵T的儿子子树的队伍与它匹配,而此时这两支队伍的最短路一定经过T(因为T是他们的LCA)。

先用treedp找出来选哪个城市,然后用一遍dfs处理每个儿子子树所含的队伍原城市,存到一个队列里,再用一个优先队列进行分配即可,注意特判当T为队伍原城市时那它去跟剩下优先队列没处理的那个城市匹配。

#include<iostream>
#include<algorithm>
#include<math.h>
#include<string.h>
#include<stdio.h>
#include<vector>
#include<queue>
using namespace std;
const int N=200050;
int head[N],to[2*N],next[2*N],cnt,mx[N],size[N];
int ans1;
int n,k;
struct state
{
	int siz,dui;
	friend bool operator < ( state a,state b)
	{
		return a.siz<b.siz;
	}
};
priority_queue<state> q;
void add(int u,int v)
{
	to[++cnt]=v;
	next[cnt]=head[u];
	head[u]=cnt;
}
int u,v;
queue<int> tu[N];
int x[N],h[N];
void dfs(int now,int last)
{
	mx[now]=0;
	for(int i=head[now];i!=-1;i=next[i])
	{
		if(to[i]==last)
			continue;
		dfs(to[i],now);
		size[now]+=size[to[i]];
		mx[now]=max(mx[now],size[to[i]]);
	}
	if(h[now]==1)
		size[now]++;
	mx[now]=max(mx[now],2*k-size[now]);
	if(mx[now]<=k&&(!ans1))
		ans1=now;
}
int ax[N];
void  dfs2(int now,int flag,int last)
{
	if(h[now])
		tu[flag].push(now);
	for(int i=head[now];i!=-1;i=next[i])
	{
		if(to[i]!=last)
			dfs2(to[i],flag,now);	
	}	
}
struct daan
{
	int f,t;
}ans2[N];
int num=0;
void solve()
{
	int cnt1=0;
	for(int i=head[ans1];i!=-1;i=next[i])
	{
		dfs2(to[i],++cnt1,ans1);
		if(tu[cnt1].size())
		q.push((state){tu[cnt1].size(),cnt1});
	}
	state maxx=q.top();
	q.pop();
	while(!q.empty())
	{
		state now=q.top();
		q.pop();
		if(now.siz>maxx.siz)
		{
			q.push(maxx);
			maxx=now;
			continue;
		}
		ans2[++num].f=tu[now.dui].front();
		tu[now.dui].pop();
		ans2[num].t=tu[maxx.dui].front();
		tu[maxx.dui].pop();
		maxx.siz--;
		now.siz--;
		if(now.siz)
			q.push(now);
		if(q.empty()&&maxx.siz)
		{
			maxx.siz--;
			ans2[++num].f=tu[maxx.dui].front();
			tu[maxx.dui].pop();
			ans2[num].t=ans1;
			break;
		}
		if(!maxx.siz)
		{
			if(q.empty())
				break;
			else
				maxx=q.top(),q.pop();
		}
	}
	if(maxx.siz)
	{
		maxx.siz--;
		ans2[++num].f=tu[maxx.dui].front();
		ans2[num].t=ans1;
	}
}
int main()
{
	memset(head,-1,sizeof(head));
	cin>>n>>k;
	for(int i=1;i<n;i++)
		scanf("%d%d",&u,&v),add(u,v),add(v,u);
	for(int i=1;i<=2*k;i++)
		scanf("%d",&x[i]),h[x[i]]++;
	dfs(1,0);
	cout<<1<<endl;
	cout<<ans1<<endl;
	solve();
	for(int i=1;i<=num;i++)
		cout<<ans2[i].f<<" "<<ans2[i].t<<" "<<ans1<<endl;
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值