2019.09.15 多校联合训练(提高组)

能量获取

一、题目

“封印大典启动,请出Nescafe魂珠!”随着圣主applepi一声令下,圣剑护法rainbow和魔杖护法freda将Nescafe魂珠放置于封印台上。封印台是一个树形的结构,魂珠放置的位置就是根节点(编号为0)。还有n个其他节点(编号1-n)上放置着封印石,编号为i的封印石需要从魂珠上获取Ei的能量。能量只能沿着树边从魂珠传向封印石,每条边有一个能够传递的能量上限Wi,魂珠的能量是无穷大的。作为封印开始前的准备工作,请你求出最多能满足多少颗封印台的能量需求?

注意:能量可以经过一个节点,不满足它的需求而传向下一个节点。每条边仅能传递一次能量。

二、解法

0x01 树形dp
我觉得大家应该都想的出来,直接跑背包, O ( n m 2 ) O(nm^{2}) O(nm2)
0x02 贪心
排序后选消耗最小的点,把它到根的路径全部减去他的消耗,贡献答案,用树链剖分维护可以做到 O ( n l o g n ) O(nlogn) O(nlogn)

#include <cstdio>
#include <iostream>
using namespace std;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,tot,E[1005],dp[1005][105],f[1005],ans;
struct edge
{
	int v,c,next;
}e[2005];
void dfs(int u,int fa,int pre)
{
	for(int i=E[u];i<=pre;i++) 
		dp[u][i]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v,c=e[i].c;
		if(v==fa) continue;
		dfs(v,u,c);
		if(u)
			for(int j=pre;j>=0;j--)
				for(int k=0;k<=j && k<=c;k++)
					dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]);
		else
			ans+=dp[v][c];
	}
}
int main()
{
	n=read();
	for(int i=1,j,c;i<=n;i++)
	{
		j=read(),E[i]=read(),c=read();
		e[++tot]=edge{j,c,f[i]},f[i]=tot;
		e[++tot]=edge{i,c,f[j]},f[j]=tot;
	}
	dfs(0,0,0);
	printf("%d\n",ans);
}

封印一击

一、题目

“圣主applepi于公元2011年9月创造了Nescafe,它在散发了16吃光辉之后与公元2011年11月12日被封印为一颗魂珠,贮藏于Nescafe神塔之中。公元2012年9月,圣主带领四大护法重启了Nescafe,如今已经是Nescafe之魂的第30吃传播了。不久,它就要被第二次封印,而变成一座神杯。。。”applepi思索着Nescafe的历史,准备着第二次封印。

Nescafe由n种元素组成(编号为1~n),第i种元素有一个封印区[ai,bi]。当封印力度E小于ai时,该元素获得ai的封印能量;当封印力度E在ai到bi之间时,该元素将获得E的封印能量;而当封印力度E大于bi时,该元素将被破坏从而不能获得任何封印能量。现在圣主applepi想选择恰当的E,使得封印获得的总能量尽可能高。为了封印的最后一击尽量完美,就请你写个程序帮他计算一下吧!

二、解法

贪心地看,答案一定是某个区间的右端点。
我们枚举所有又端点,发现可以将每个右端点对应的答案分成三部分。
1、 a a a大于 b [ i ] b[i] b[i]的,可以排序后 u p p e r _ b o u n d upper\_bound upper_bound求出范围,前缀和处理即可。
2、 b b b小于 b [ i ] b[i] b[i]的,可以排序后 l o w e r _ b o u n d − 1 lower\_bound-1 lower_bound1求出范围,不去计算。
3、 a ≤ b [ i ] ≤ b a\leq b[i]\leq b ab[i]b的,减去前两个的个数,乘上 b [ i ] b[i] b[i]即可。

#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 100005;
#define int long long
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,Max,ans,a[MAXN],b[MAXN],pre[MAXN];
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),b[i]=read();
	sort(a+1,a+1+n);
	sort(b+1,b+1+n);
	for(int i=1;i<=n;i++)
		pre[i]=pre[i-1]+a[i];
	for(int i=1;i<=n;i++)
	{
		int up=upper_bound(a+1,a+1+n,b[i])-a;
		int dow=lower_bound(b+1,b+1+n,b[i])-b-1;
		int E=n-(n-up+1)-dow;
		int val=pre[n]-pre[up-1]+E*b[i];
		if(val>Max)
		{
			Max=val;
			ans=b[i];
		}
	}
	printf("%lld %lld\n",ans,Max); 
}

归途与征程

一、题目

给出一个长度为N的由小写字母’a’‘z’和’‘组成的字符串A,一个长度为M的仅由小写字母’a’‘z’组成的字符串B。一个’'可以匹配任意多个字符(包括0个)。求在B的所有循环同构串中,有多少个能够与A匹配。

循环同构串:就是把B的前k个字母(0≤k<M)移到结尾所得到的字符串。例如abc的循环同构串由abc、bca和cab。

A与B匹配:若除了A中的’ * '可以匹配B中的任意多个字符串外,其余字符一一对应,则称A与B匹配。例如abc与aadbc是匹配的,其中第一个对应ad,第二个对应空串。

原题参考:http://codeforces.com/gym/100113 中的G题

二、解法

考试时方法很接近了,统计答案时弄错了 q w q qwq qwq
既然是一个环串,我们把原串扩大2倍,枚举起点即可。
A A A串按星号划分成若干个独立的串,每个串放到 B B B中跑 H a s h Hash Hash,处理处 d p [ i ] [ j ] dp[i][j] dp[i][j],即对于第 j j j A A A的独立串在 B B B串中匹配成功的距离 i i i最近的起点下标(可能有点绕,多读几遍理解一下),这样对于每个独立串,我们就可以直接跳到下一个独立串的匹配。
处理好之后我们来考虑怎么统计答案。我们枚举 B B B的起点,一个一个跳即可,我们需要特判掉开头和结尾不是星号的情况,不想多说,看代码吧。

#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
#define ull unsigned long long
const int MAXN = 200005;
int read()
{
	int x=0,flag=1;char c;
	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*flag;
}
int n,m,le[105],ans,cnt,dp[MAXN][105];
ull Hash[MAXN],Pow[MAXN]={1};
char a[105],b[MAXN];
ull get_hash(int l,int r)
{
	return Hash[r]-Hash[l-1]*Pow[r-l+1];
}
void make(int len,string x)
{
	ull H=0;cnt++;
	if(x=="") return ;
	le[cnt]=len;
	for(int i=0;i<len;i++)
		H=H*371ll+x[i];
	for(int i=1;i<=m-len+1;i++)
	{
		if(H==get_hash(i,i+len-1))
			dp[i][cnt]=i;
	}
	for(int i=m;i>=1;i--)
		if(!dp[i][cnt])
			dp[i][cnt]=dp[i+1][cnt];
}
int main()
{
	scanf("%s",a+1);
	scanf("%s",b+1);
	n=strlen(a+1);m=strlen(b+1);
	for(int i=1;i<=m;i++)
		b[i+m]=b[i];
	m<<=1;
	for(int i=1;i<=m;i++)
		Pow[i]=Pow[i-1]*371ll;
	for(int i=1;i<=m;i++)
		Hash[i]=Hash[i-1]*371ll+b[i];
	for(int i=1;i<=n;i++)
	{
		string x="";int j=i;
		if(a[i]=='*') continue;
		for(;j<=n && a[j]^'*';j++)
			x+=a[j];
		make(j-i,x);
		i=j-1;
	}
	for(int i=1;i<=m/2;i++)
	{
		int j=n,flag=1,t,l;
		for(;j>=1 && a[j]!='*';j--)
			if(a[j]!=b[m/2+i-1-(n-j)]) 
			{
				flag=0;
				break;
			}
		if(!flag || (j==0 && n!=m/2)) continue;
		for(t=1,l=1;t<=cnt-(a[n]!='*');t++)
		{
			if(t==1 && a[1]^'*' && dp[i][1]^i) break;
			l=dp[i][t]+le[t];
		}
		if(t>cnt-(a[n]!='*') && l<=i+m/2) ans++;
	}
	printf("%d\n",ans); 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值