纪中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
<
t
e
m
p
<
s
u
m
b
+
l
e
n
sumb < temp < 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
Input
Output
输出文件名为linear.out。
输出一行两个自然数,用空格隔开,依次为L和R。
Sample Input
5
1 3
2 3
3 5
5 5
6 7
Sample Output
3 5
Data Constraint
简要思路:本题有很多人用三分大法求解,本人表示看不出(然而,三分可行证明了函数是单极峰的),我用的方法十分投机,把所有数收在一起,排序,输出中间两个即可。
证明嘛,我不会,但也找不到反例,下面给出一个大佬的证明:
我们设取到的其中一个极值点在 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=1∑n[li≤w]+[ri≤w], u = ∑ i = 1 n [ l i > w ] + [ r i > w ] u = \sum_{i=1}^n [l_i \gt w ] + [r_i \gt w ] u=i=1∑n[li>w]+[ri>w],其中 [ x ] [x] [x] 当 x x x成立时值为1,否则为0。
我们显然要让 ∣ u − v ∣ |u - v| ∣u−v∣的值尽量小,函数值才会小。
所以我们把所有的左右边界丢在一起排个序,取中间两个就是答案了。(玩深沉提高严谨性)
//还有必要吗
#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;
}