纪中DAY8做题小结

T1:少女觉

Description
在幽暗的地灵殿中,居住着一位少女,名为古明地觉。
据说,从来没有人敢踏入过那座地灵殿,因为人们恐惧于觉一族拥有的能力——读心。
掌控人心者,可控天下。
咳咳。
人的记忆可以被描述为一个黑块(B)与白块(W)的序列,其中情感值被定义为序列中黑块数量与白块数量之比。
小五口在发动读心术时,首先要解析人的记忆序列,因此,需要将序列分割为一些段,并且要求每一段记忆序列的情感值都相等。
下面给出两个例子:
BWWWBB -> BW + WWBB (Ratio=1:1)
WWWBBBWWWWWWWWWB -> WWWB + BBWWWWWW + WWWB (Ratio=3:1)
现在小五手上有一个人的记忆序列,她想要知道,如何将手中的记忆序列分成尽可能多的段呢?

Input
第一行包含一个正整数T,代表数据组数。
对于每一组测试数据,第一行包含一个正整数N。
接下来N行描述一个序列,每行包含一个正整数K和一个大写字母C,表示序列接下来有连续K个颜色为C的方块。

Output
对于每组测试数据输出一行一个正整数,表示最多分成的段数。

Sample Input
3
3
1 B
3 W
2 B
4
3 W
3 B
9 W
1 B
2
2 W
3 W

Sample Output
2
3
5

Data Constraint
对于10%的数据,n<=15
对于20%的数据,n<=500
另有30%的数据,K=1
另有30%的数据,K<=50
对于100%的数据,N<=10 ^ 5,序列长度不超过10 ^ 9
保证对于全部测试点,输入文件行数不超过2.5*10 ^ 6

简要思路:本体其实是一道普通的贪心题,然而我在考试时愣是把问题复杂化,从而给自己挖了一个坑 。在本题中每一个被划分的字符串中B : W的比例相同,由公式 a + c b + d = a b + c d \frac{a + c}{b+d} = \frac{a}{b} + \frac{c}{d} b+da+c=ba+dc多此一举了吧 )得出整个序列中B : W的比例与每个分序列的相同,并且序列能分就分,不会对后面的值造成影响。实现时首先要特判,如果只有一种字符那直接输出字符串的长度,然后,通过求最大公因数将B : W比例化到最简。接下来,从头到尾扫描一遍,用变量 s u m b sumb sumb s u m w sumw sumw记录已扫描到的b,w的数目。当扫描到B的区间时,设当前区间长为 l e n len len用比例公式计算当前已扫描到的W的总数所对应的期望的B的总数,设为temp,若 s u m b &lt; t e m p &lt; s u m b + l e n sumb &lt; temp &lt; sumb + len sumb<temp<sumb+len,则可从该区间中间或边缘分割,答案加1,B,W对调也成立。

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const int N = 1e5 + 5;
int t , n , sumb , sumw , totb , totw , num , ans;
int len[N] , pos[N];//pos中0代表b,1代表W 
char ss;
inline int gcd( int x , int y ) {
	return y == 0 ? x : gcd( y , x % y );
}
int main () {
	//freopen( "silly.in" , "r" , stdin );
	//freopen( "silly.out" , "w" , stdout );
	scanf("%d",&t);
	while ( t-- ) {
		scanf("%d",&n);
		sumb = 0;
		sumw = 0;
		totb = 0;
		totw = 0;
		ans = 0;
		for ( int i = 1 ; i <= n ; ++i ) {
			scanf("%d",&num);
			len[i] = num;
			getchar();
			ss = getchar();
			switch(ss) {
				case 'W' : {
					totw += num;
					pos[i] = 1;
					break;
				}
				case 'B' : {
					totb += num;
					pos[i] = 0;
					break;
				}
			}
		}
		if ( !totb || !totw ) {
			printf("%d\n",max( totb , totw ));
			continue;
		}
		int d = gcd( totb , totw );
		totb /= d;
		totw /= d;
		int tem;
		for ( int i = 1 ; i <= n ; ++i ) {
			if ( !pos[i] ) {
				if ( sumw % totw == 0 ) {
					tem = ( (ll)sumw * totb ) / totw;//相乘可能会爆int,要开long long
					if ( tem > sumb && tem <= sumb + len[i] ) {
						ans++;
					}
				}
				sumb += len[i];
			} else {
				if ( sumb % totb == 0 ) {
					tem = ( (ll)sumb * totw ) / totb;//相乘可能会爆int,要开long long
					if ( tem > sumw && tem <= sumw + len[i] ) {
						ans++;
					}
				}
				sumw += len[i];
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

T2:灵知的太阳信仰

Description
在炽热的核熔炉中,居住着一位少女,名为灵乌路空。
据说,从来没有人敢踏入过那个熔炉,因为人们畏缩于空所持有的力量——核能。
核焰,可融真金。
咳咳。
每次核融的时候,空都会选取一些原子,排成一列。然后,她会将原子序列分成一些段,并将每段进行一次核融。
一个原子有两个属性:质子数和中子数。
每一段需要满足以下条件:
1、同种元素会发生相互排斥,因此,同一段中不能存在两个质子数相同的原子。
2、核融时,空需要对一段原子加以防护,防护罩的数值等于这段中最大的中子数。换句话说,如果这段原子的中子数最大为x,那么空需要付出x的代价建立防护罩。求核融整个原子序列的最小代价和。

Input
第一行一个正整数N,表示原子的个数。
接下来N行,每行两个正整数pi和ni,表示第i个原子的质子数和中子数。

Output
输出一行一个整数,表示最小代价和。

Sample Input
5
3 11
2 13
1 12
2 9
3 13

Sample Output
26

Data Constraint
对于20%的数据,1<=n<=100
对于40%的数据,1<=n<=1000
对于100%的数据,1<=n<=10 ^ 5,1<=pi<=n,1<=ni<=2*10 ^ 4

简要思路:本来呢,这题暴力是只能拿四十分的,但是,有一位大佬证明,有技巧的暴力是能过的(每次得到一个位置i向左,走到相同数为止),这有一个栗子

(大佬的证明)假设 i 步就结算,那么花费了 i 步,概率是 i-1 步内未结束,在第 i 步时结束了。
P(i-1 步内未结束)=P(前 i-1 个互不相同)=(p/p)[(p-1)/p][(p-2)/p]……[(p-i)/p]=p!/[(p-i-1)!*p^(i-1)]
P(在第 i 步恰好结束)=P(i-1 步内未结束)*P(第 i 个与前 i-1 个中某一个相同)=p!/[(p-i-1)!p^(i-1)](i-1)/p
对期望的贡献为 P(在第 i 步恰好结束)*i
我们计算一下 10^5 时的期望,大约为 400+。
因此该暴力算法的期望复杂度确实能通过全部的数据。

不过,我们为了追求不被有心的出题人卡,应该想一个更优的方法(单调栈要登场了)。
根据题意,不难推出方程 d p [ i ] = d p [ j ] + m a x dp[i] = dp[j] + max dp[i]=dp[j]+max{ j + 1 j + 1 j+1 ~ i i i };用数组 m a x n maxn maxn记录加号后面式子的值。
同时, l l l数组记录每个数能向左拓展的最大范围,用单调队列维护数据, h e a d head head的位置在当前值最大范围以外,踢出;对于 t a i l tail tail,踢出中子数不如当前中子数的,维护一个单调递增的单调栈
处理完后,当前值就可以入单调队列了。
不过此时有两种情况:
如果队列中只有一个元素(刚加进来的),直接更新;
如果不止一个,由于最靠前的在当前值的活动范围之外,要特地更新一下(先出来再进去)。
还有疑问的看代码吧。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <set>
using namespace std;
const int N = 1e5 + 5;
int numa[N] , numb[N] , l[N] , now[N] , sum[N] , maxn[N] , pos[N] , dp[N];
multiset <int> q;//它的作用是自动排序,并删除指定值,multiset可存值相同的元素 
int n , head , tail;
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 int ma( int l , int r ) {
	int ans = -100;
	for ( int i = l ; i <= r ; ++i ) {
		ans = max( ans , numb[i] );
	}
	return ans;
}
int main () {
	//freopen( "array.in" , "r" , stdin );
	//freopen( "array.out" , "w" , stdout );
	read(n);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(numa[i]);
		read(numb[i]);
		l[i] = now[numa[i]] + 1;//统计当前数的活动范围 
		now[numa[i]] = i;
		l[i] = max( l[i] , l[i - 1] );//左边数的范围也会对当前数的范围造成限制 
	}
	head = 1;
	tail = 0;
	for ( int i = 1 ; i <= n ; ++i ) {
		int k = i - 1;
		while ( head < tail && l[i] > pos[head + 1] ) {//踢出活动范围以外的 
			q.erase(q.find(sum[head]));//其实留了一个,对应后面改队头的操作
			head++;
		}
		while ( head <= tail && numb[i] > maxn[tail] ) {//维护单调栈 
			q.erase(q.find(sum[tail]));
			k = pos[tail]; 
			tail--;
		}
		pos[++tail] = k;
		maxn[tail] = numb[i];
		if ( head != tail ) {
			sum[tail] = dp[pos[tail]] + maxn[tail];
			q.insert(sum[tail]);
			q.erase(q.find(sum[head]));
		}
		sum[head] = dp[l[i] - 1] + maxn[head];
		q.insert(sum[head]);
		dp[i] = *q.begin();
	}
	printf("%d",dp[n]);
	return 0;
}

T3:多段线性函数

Description
图1

Input
图2

Output
输出文件名为linear.out。
输出一行两个自然数,用空格隔开,依次为L和R。

Sample Input
5
1 3
2 3
3 5
5 5
6 7

Sample Output
3 5

Data Constraint
图3

简要思路:本题有很多人用三分大法求解,本人表示看不出(然而,三分可行证明了函数是单极峰的),我用的方法十分投机,把所有数收在一起,排序,输出中间两个即可。
证明嘛,我不会,但也找不到反例,下面给出一个大佬的证明:

我们设取到的其中一个极值点在 w w w,再设两个值 u = ∑ i = 1 n [ l i ≤ w ] + [ r i ≤ w ] u = \sum_{i=1}^n [l_i \le w ] + [r_i \le w ] u=i=1n[liw]+[riw], u = ∑ i = 1 n [ l i &gt; w ] + [ r i &gt; w ] u = \sum_{i=1}^n [l_i \gt w ] + [r_i \gt w ] u=i=1n[li>w]+[ri>w],其中 [ x ] [x] [x] x x x成立时值为1,否则为0。
我们显然要让 ∣ u − v ∣ |u - v| uv的值尽量小,函数值才会小。
所以我们把所有的左右边界丢在一起排个序,取中间两个就是答案了。(玩深沉提高严谨性)

//还有必要吗
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5 + 5;
int n , num[2 * 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;
}
int main () {
	//freopen( "linear.in" , "r" , stdin );
	//freopen( "linear.out" , "w" , stdout );
	read(n);
	int l , r;
	for ( int i = 1 ; i <= n ; ++i ) {
		read(l);
		read(r);
		num[i * 2 - 1] = l;
		num[i * 2] = r;
	}
	sort( num + 1 , num + 1 + 2 * n );
	int nn = n * 2;
	printf("%d %d",num[nn / 2],num[nn / 2 + 1]);
}

T4:DY引擎

Description
BOSS送给小唐一辆车。小唐开着这辆车从PKU出发去ZJU上课了。
众所周知,天朝公路的收费站超多的。经过观察地图,小唐发现从PKU出发到ZJU的所有路径只会有N(2<=N<=300)个不同的中转点,其中有M(max(0, N-100) <=M<=N)个点是天朝的收费站。N个中转点标号为1…N,其中1代表PKU,N代表ZJU。中转点之间总共有E(E<=50,000)条双向边连接。
每个点还有一个附加属性,用0/1标记,0代表普通中转点,1代表收费站。当然,天朝的地图上面是不会直接告诉你第i个点是普通中转点还是收费站的。地图上有P(1<=P<=3,000)个提示,用[u, v, t]表示:[u, v]区间的所有中转点中,至少有t个收费站。数据保证由所有提示得到的每个点的属性是唯一的。
车既然是BOSS送的,自然非比寻常了。车子使用了世界上最先进的DaxiaYayamao引擎,简称DY引擎。DY引擎可以让车子从U瞬间转移到V,只要U和V的距离不超过L(1<=L<=1,000,000),并且U和V之间不能有收费站(小唐良民一枚,所以要是经过收费站就会停下来交完钱再走)。
DY引擎果然是好东西,但是可惜引擎最多只能用K(0<=K<=30)次。

Input
第一行有6个整数N,M,E,P,L,K分别代表:N个中转点,M个收费站,E条边,P个提示,DY引擎的有效距离L,DY引擎的使用次数K。
接下去E行,每行有3个整数u,v,w(1<=u, v<=N; 1<=w<=1,000,000)表示:u和v之间有一条长度为w的双向边。
接下去P行,每行有3个整数u,v,t(1<=u<=v<=N; 0<=t<=u-v+1)表示: [u, v] 标号区间至少有t个收费站。

Output
输出一个整数,表示小唐从PZU开到ZJU用的最短距离(瞬间转移距离当然是按0来计算的)。

Sample Input
6 2 6 2 5 1
1 2 1
2 3 2
3 6 3
1 4 1
4 5 2
5 6 3
2 5 2
4 6 2

Sample Output
1【样例解释】
4、5是收费站。1->2(1)->6(1)

Data Constraint
对于30%的数据保证:
2<=N<=30,max(0, N-10) <=M<=N,0<=k<=10
对于100%的数据保证:
2<=N<=300,max(0, N-100) <=M<=N,E<=50,000,1<=P<=3,000,1<=L<=1,000,000,0<=K<=30.

简要思路:一道没意思的套路码农题,差分约束系统加上SPFA即可。对于不会的差分约束系统的,我推荐这篇博客这篇博客不是我的 )。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 305 ,  M = 1000005;
int n , m , ptot , p , l ,  k , head , tail;
int q[M] , d1[N] , map1[N][N] , d2[N][N] , map2[N][N] , vis[N] , t[N][N] , qq[M][2] , viss[N][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;
}
void doit() {
	d1[n] = ptot;
	d1[0] = 0;
	vis[n] = 1;
	head = tail = 1;
	q[tail] = n;
	while ( head <= tail ) {
		int cur = q[head];
		head++;
		for ( int i = 1 ; i <= n ; ++i ) {
			if ( i != cur ) {
				if ( d1[cur] + map1[cur][i] < d1[i] ) {
					d1[i] = d1[cur] + map1[cur][i];
					if ( !vis[i] ) {
						q[++tail] = i;
						vis[i] = 1;
					}
				}
			}
		}
		vis[cur] = 0;
	}
}
void floyd() {
	for ( int k = 1 ; k <= n ; ++k ) {
		if ( d1[k] - d1[k - 1] <= 0 ) {
			for ( int i = 1 ; i <= n ; ++i ) {
				for ( int j = 1 ; j <= n ; ++j ) {
					if ( i != j && i != k && j != k ) {
						t[i][j] = min( t[i][j] , t[i][k] + t[k][j] );
					}
				}
			}
		}
	}
	return;
}
void spfa() {
	head = tail = 1;
	qq[tail][0] = 1;
	qq[tail][1] = 0;
	viss[1][0] = 1;
	d2[1][0] = 0;
	while ( head <= tail ) {
		int cur = qq[head][0];
		int tim = qq[head][1];
		head++;
		for ( int i = 2 ; i <= n ; ++i ) {
			if ( i != cur ) {
				if ( d2[cur][tim] + map2[cur][i] < d2[i][tim] ) {
					d2[i][tim] = d2[cur][tim] + map2[cur][i];
					if ( !viss[i][tim] ) {
						qq[++tail][0] = i;
						qq[tail][1] = tim;
						viss[i][tim] = 1;
					}
				}
				if ( tim < k && t[cur][i] <= l && d2[i][tim + 1]  > d2[cur][tim] ) {
					d2[i][tim + 1] = d2[cur][tim];
					if ( !viss[i][tim + 1] ) {
						qq[++tail][0] = i;
						qq[tail][1] = tim + 1;
						viss[i][tim + 1] = 1;
					}
				}
			}
		}
		viss[cur][tim] = 0;
	}
}
int main () {
	//freopen( "dy4.in" , "r" , stdin );
	read(n);
	read(ptot);
	read(m);
	read(p);
	read(l);
	read(k);
	int u , v , tt;
	memset( map1 , 0x3f , sizeof(map1) );
	memset( map2 , 0x3f , sizeof(map2) );
	memset( t , 0x3f , sizeof(t) );
	memset( d1 , 0x3f , sizeof(d1) );
	memset( d2 , 0x3f , sizeof(d2) );
	for ( int i = 1 ; i <= m ; ++i ) {
		read(u);
		read(v);
		read(tt);
		map2[u][v] = map2[v][u] = min( map2[u][v] , tt );
		t[u][v] = t[v][u] = map2[u][v];
	}
	for ( int i = 1 ; i <= p ; ++i ) {
		read(u);
		read(v);
		read(tt);
		if ( map1[v][u - 1] > -tt ) {
			map1[v][u - 1] = -tt;
		}
	}
	for ( int i = 1 ; i <= n ; ++i ) {
		map2[i][i] = 0;
		map1[i - 1][i] = 1;
		map1[i][i - 1] = 0;
	}
	doit();//差分约束
	floyd();//预处理开挂路径长
	spfa();//跑路
	int ans = 100000005;
	for ( int i = 0 ; i <= k ; ++i ) {
		ans = min( ans , d2[n][i] );
	}
	printf("%d\n",ans);
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值