纪中DAY12做题小结

T1:能量获取

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

Input
第一行一个整数n,表示除根节点之外的其他节点的数量。
接下来n行,第i+1行有三个整数Fi、Ei、Wi,分别表示i号节点的父节点、i号节点上封印石的能量需求、连接节点i与Fi的边最多能传递多少能量。

Output
最多能满足多少颗封印石的能量需求。

Sample Input

4 
0 3 2
0 100 100
1 1 1
2 75 80

Sample Output

2

Data Constraint
对于100%的数据,满足1<=n<=1000,0<=Fi<=n,0<=Ei,Wi<=100

简要思路:这道题主要思想是贪心,即贪心地选一个满足条件且需要能量最少的点,并且向上传减少的能量。至于证明吧,还是那句话,感性理解吧 。首先,假设有一个最优解,它不包括满足条件的能量最少的点,那么此时只要在合适的地方选择不满足一个点的能量,将省下的能量传递给那个能量需求最小且符合题意的点,此时剩下的总能量(因为满足不大于边,实际可利用总能量是有限的)更多,有更多的希望满足更多的点。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1005;
int n , bcnt , tot;
bool flag;
int fa[N] , val[N];
struct no{
	int pos;
	int need;
}node[N];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline bool cmp( no a , no b ) {
	return a.need < b.need;
}
int main () {
	read(n);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(fa[i]);
		read(node[i].need);
		read(val[i]);
		node[i].pos = i;
	}
	tot = 0;
	sort( node + 1 , node + 1 + n , cmp );
	for ( int i = 1 ; i <= n ; ++i ) {
		flag = true;
		for ( int j = node[i].pos ; j ; j = fa[j] ) {
			if ( val[j] < node[i].need ) {
				flag = false;
				break;
			}
		}
		if ( !flag ) {
			continue;
		}
		tot++;
		for ( int j = node[i].pos ; j ; j = fa[j] ) {
			val[j] -= node[i].need;
		}
	}
	printf("%d",tot);
	return 0;
}

T2:封印一击

Description
“圣主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,使得封印获得的总能量尽可能高。为了封印的最后一击尽量完美,就请你写个程序帮他计算一下吧!

Input
第一行一个整数N。
接下来N行每行两个整数ai、bi,第i+1行表示第i种元素的封印区间。

Output
两个用空格隔开的证书,第一个数十能够获得最多总能量的封印力度E,第二个数是获得的总能量大小。当存在多个E能够获得最多总能量时,输出最小的E。

Sample Input

2
5 10
20 25

Sample Output

10 30

Data Constraint
对于50%的数据,1<=N<=1000,1<=ai<=bi<=10000。
对于100%的数据,1<=N<=10 ^ 5,1<=ai<=bi<=10 ^ 9。

简要思路:这道题是一道贪心加上模拟的题目。由题意知,若选定一些区间,规定一定超越右节点比所选定区间最小右节点小的区间,并且不超越所有选定区间的右节点,那么这一组解中,最优的一定是选定区间的最小右节点。而解的所有情况可由按上述规则选取区间并求出答案一一对应,所以,最优解一定在某个区间的最右端。枚举每个区间最右端,并注意细节即可。细节处理得好无需用线段树,树状数组,离散化哦。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define ll long long
using namespace std;
const int N = 100005;
ll n , e , maxn , tot;
int pcnt;
struct no{
	int col;
	ll val;
}now[2 * N];
inline void read( ll & res ) {
	res = 0;
	ll pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
inline bool cmp( no a , no b ) {
	return a.val < b.val || (a.val == b.val && a.col < b.col);
}
int main () {
	read(n);
	tot = 0;
	maxn = 0;
	pcnt = 0;
	ll x , y;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(x);
		read(y);
		tot += x;
		now[i].col = 1;
		now[i].val = x;
		now[i + n].col = 2;
		now[i + n].val = y;
	}
	sort( now + 1 , now + 1 + 2 * n , cmp );
	for ( int i = 1 ; i <= 2 * n ; ++i ) {
		if ( now[i].col == 1 ) {
			tot -= now[i].val;
			pcnt++;
		} else {
			ll ans = pcnt * now[i].val + tot;//pcnt代表超越左端点但未过右端点的个数,tot代表未到达的左端点值的总和
			if ( ans > maxn ) {
				maxn = ans;
				e = now[i].val;
			}
			pcnt--;
		}
	}
	printf("%lld %lld",e,maxn);
	return 0;
}

T3:归途与征程

Description
图1

Input
第一行为字符串A。
第二行为字符串B。

Output
输出在B的所有循环同构串中,有多少个能够与A匹配。

Sample Input
输入1:

aaaa
aaaa

输入2:

a*a
aaaaaa

输入3:

*a*b*c*
abacabadabacaba

Sample Output
输出1:

4

输出2:

6

输出3:

15

Data Constraint
对于30%的数据,M<=20;
对于80%的测试点,M<=200;
对于100%的测试点,1<=N<=100,1<=M<=100000。

简要思路:本题是一道难度高的模拟题。对于字符串A,我们可以先将’*'隔开的子串用KMP处理一下(子串最多只有 N / 2 N/2 N/2个,数组不要开太大), f [ i ] [ j ] f[i][j] f[i][j]计录字符串B中 i i i位置向后第 f [ i ] [ j ] f[i][j] f[i][j]位可以开始匹配完字符串A的第 j j j个子串。最后说一下,注意特判。

这个代码在GMOJ可A,但在codeforce gym上会MLE(炸空间)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int N = 105 , M = 100005;
int n , m , tot , ans;
char a[N] , b[M * 2];
int pds , pde;//判断头尾是否为‘*’
char str[N];//被‘*’分开的字符串a
int len[N / 2];//上面字符串的长度
int nxt[N];//KMP的前缀数组
int f[M * 2][N / 2];//字符串b从第i位开始第一个能匹配第j个字符串str的位置
int pd[M * 2][N / 2];//表示a从i开始往后能否匹配到第j个str
inline void kmp( int k ) {
	int j = 0;
	nxt[1] = 0;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		while ( j && str[i + 1] != str[j + 1] ) {
			j = nxt[j]; 
		}
		if ( str[i + 1] == str[j + 1] ) {
			j++;
		}
		nxt[i + 1] = j;
	}
	j = 0;
	for ( int i = 0 ; i <= 2 * m - 1 ; ++i ) {
		while ( j && str[j + 1] != b[i + 1] ) {
			j = nxt[j];
		}
		if ( str[j + 1] == b[i + 1] ) {
			j++;
		}
		if ( j == len[k] ) {
			pd[i + 1 - j + 1][k] = 1;
		}
	}
	f[2 * m + 1][k] = 0x3f3f3f3f;
	for ( int i = 2 * m ; i >= 1 ; i-- ) {
		if ( pd[i][k] ) {
			f[i][k] = i;
		} else {
			f[i][k] = f[i + 1][k];
		}
	}
	return;
}
inline bool check( int k ) {
	int r = k + m - 1;
	int l = k;
	int head = 1;
	int tail = tot;
	if ( tot == 1 && !pds && !pde ) {//特判无'*'的情况
		if ( pd[l][1] ) {
			return true;
		} else {
			return false;
		}
	}
	if ( !pds ) {
		if ( !pd[l][1] ) {
			return false;
		}
		head++;
		l += len[1];
	}
	if ( !pde ) {
		if ( !pd[r - len[tot] + 1][tot] ) {
			return false;
		}
		tail--;
		r -= len[tot];
	}
	while ( head <= tail ) {
		l = f[l][head];
		if ( l > r ) {
			return false;
		}
		l += len[head] - 1;
		head++;
	}
	if ( l > r ) {
		return false;
	} else {
		return true;
	}
}
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];
	}
	if ( a[1] == '*' ) {
		pds = 1;
	} else {
		pds = 0;
	}
	if ( a[n] == '*' ) {
		pde = 1;
	} else {
		pde = 0;
	}
	tot = 0;
	for ( int i = 1 ; i <= n ; ++i ) {
		if ( a[i] != '*' ) {
			len[++tot] = 0;
		} else {
			continue;
		}
		while ( a[i] != '*' && i <= n ) {
			str[++len[tot]] = a[i];
			i++;
		}
		kmp( tot );
	}
	ans = 0;
	if ( tot == 1 && !pds && !pde ) {//特判无'*'的情况 
		if ( n != m ) {
			printf("0");
			return 0;
		}
	}
	for ( int i = 1 ; i <= m ; ++i ) {
		if ( check(i) ) {
			ans++;
		}
	}
	printf("%d",ans);
	return 0;
}

这个代码是我在网上找到的,在之前可以A掉codeforce gym,现在好像也不行了-_-||。
不过,这份代码非常省时间空间,值得学习。

#include <iostream>
#include <cstdio>
#include <cstring>
#define N 104
#define M 200020
using namespace std;
int now[N] , n , m , start;//now表示s字符串的下标对应t字符串的下标
char a[N] , b[M];
inline int dfs( int x , int y ) {//x代表a字符串的下标,y代表b字符串的下标 
	if ( x == n ) {
		if ( y > start + m ) return 2;//b字符串匹配越界(没有回溯的机会)
		if ( y == start + m ) return 1;//成功匹配 
		return 0;//b字符串未匹配完(有回溯的机会) 
	}
	if ( y >= m * 2 ) {//完全越界 
		return 0;
	}
	y = max( y , now[x] );
	if ( a[x] == '*' ) {//模拟 
		while ( y < m * 2 ) {
			now[x] = y;
			int t = dfs( x + 1 , y );
			if ( t ) {
				return t;
			}
			++y;
		}
	} else if ( a[x] == b[y] ) {//模拟
		now[x] = y;
		int t = dfs( x + 1 , y + 1 );
		if ( t ) { 
			return t;
		}
	}
	return 0;
}
int main() {
	scanf("%s",a);
	scanf("%s",b);
	n = strlen(a);
	m = strlen(b);
	if ( a[0] == '*' ) {//看上去有点玄学,实际上,是为了让a前面的'*'多一个,既能尽量多覆盖b的内容又避免now[0]的非法覆盖 
		for ( int i = n ; i >= 1 ; --i ) {
			a[i] = a[i - 1];
		}
		++n;
	}
	for ( int i = 0 ; i < m ; ++i ) {
		b[i + m] = b[i];
	}
	int ans = 0;
	memset( now , -1 , sizeof(now) );
	for ( start = 0 ; start < m ; ++start ) {
		if ( now[0] <= start ) {
			now[0] = start;
			if ( dfs( 0 , start ) == 1 && now[0] == start ) {
				++ans;
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

到现在我也不知道怎么在codeforce gym上A掉这题,先挖个坑以后补吧

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值