Problem.A
一个字符串被称为括号序列当且仅当该字符串中不包含除“(”和“)”之外的任何字符。如果可以通过在此序列中插入字符“+”和“1”来获得正确的算术表达式,则称该括号序列为一个匹配的括号序列(简称RBS)。例如,“”、“(())”和“()()”是RBS,而“)(”和”(()”不是RBS。
RBS中的每个左括号都与某个右括号成对,因此我们可以将RBS的嵌套深度定义为最大括号对数,这样第二对位于第一对中,第三对位于第二对中,以此类推。例如,“”的嵌套深度为0,“()()()”为1,“()((())())”为3。
现在,你得到了长度为偶数n的RBS。你应该将S的每个括号着色为两种颜色之一:红色或蓝色。括号序列R(仅由红色括号组成)为RBS,括号序列B(仅由蓝色括号组成)应为RBS。其中任何一个都可以是空的。不允许对S、R或B中的字符重新排序。括号不能不着色。
在所有可能的方案中,你应该选择一个最小化R和B的最大嵌套深度的方案。输出这个嵌套深度。
第一行包含一个整数n(2≤n≤2×105)代表RBS s的长度。
第二行包含一个匹配的括号序列s。
输出一行一个整数代表所求嵌套深度。
Example
input
2
()
output
1
input
4
(())
output
1
input
10
((()())())
output
2
Note
在第一个例子中,一种合理的方案:RR
在第一个例子中,一种合理的方案:RBRB
在第一个例子中,一种合理的方案:RBBRRRBBBB
Hint
对于30%的数据,n≤15
对于另外20%的数据,保证序列中“(”不会在“)”后出现
标程:
#include<bits/stdc++.h>
using namespace std;
int n,red,blue,ans;
string s;
int main(){
scanf("%d",&n);
cin>>s;
for(int i=0;i<n;++i){
if(s[i]==')'){
if(red>blue) --red;
else --blue;
}
else{
if(red<blue) ++red,ans=max(ans,red);
else ++blue,ans=max(ans,blue);
}
}
printf("%d",ans);
return 0;
}
30pts:2^n枚举染色方案,判断嵌套深度。 另外20pts:序列形如“(((((((((((((((((((())))))))))))))))))))”,所以输出((n/2)+1)/2即可。 100pts:用两个数分别记录红、蓝当前的嵌套深度,出现新的一层时,当前的嵌套深度哪个小,就把新的一层染成什么颜色。时间复杂度O(n)。
Problem.B
你在一棵由n个点组成的树(一个无向连通的无环图)上玩游戏。
起初,所有点都是白色的。游戏的第一步,你选择一个点并把它染成黑色。然后,接下来的每一步中,选择一个与某个黑色点相邻的白色点,并将其染为黑色。
每次选择点时,都会获得与 所选点联通的白色点的数目(包括所选点) 相等的分数。游戏结束时,所有的点都被染成黑色。
下面是一个示例:
点1和4已经被染黑。如果你选择点2,你会获得4分(联通的白色点有2,3,5,6).如果你选择点9,你会获得3分(联通的白色点有7,8,9)。
你的任务是使得分最大。
第一行包含一个整数n(2≤n≤2×105)代表树包含的点数。
接下来n-1行每行描述了树的一条边。第i行两个整数ui和vi代表ui和vi之间有边相连
(1≤ui,vi≤n,ui≠vi)。
输出一行一个整数,你能得到的最大得分。
Example
input
9
1 2
2 3
2 5
2 6
1 4
4 9
9 7
9 8
output
36
input
5
1 2
1 3
2 4
2 5
output
14
Note
第一个例子中,树的形态如题目描述所示。
Hint
对于30%的数据,n≤2,000
对于另外20%的数据,保证原图是一条链
标程:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll ans;
int n;
int sz[200004];
struct edge{
int to,nt;
}e[400004];
int ne,h[200004];
void add(int u,int v){
e[++ne].to=v;e[ne].nt=h[u];h[u]=ne;
}
void dfs(int x,int fa){
sz[x]=1;
for(int i=h[x];i;i=e[i].nt){
if(e[i].to==fa)continue;
dfs(e[i].to,x);
sz[x]+=sz[e[i].to];
}
ans+=sz[x];
}
void calc(int x,int fa,ll now){
ans=max(ans,now);
for(int i=h[x];i;i=e[i].nt){
if(e[i].to==fa)continue;
calc(e[i].to,x,now+n-2LL*sz[e[i].to]);
}
}
int main(){
memset(h,0,sizeof(h));
ne=0;
scanf("%d",&n);
for(int i=1;i<n;i++){
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs(1,0);
calc(1,0,ans);
printf("%lld\n",ans);
return 0;
}
可以发现,如果染色的第一个点确定,则最终会获得的分数也是确定的。即在根节点确定的前提下,只需要在树上dfs,在每个节点处把该点为根的子树大小加入答案即可。 30pts:O(n)枚举树的根节点,再dfs遍历O(n)计算答案,时间复杂度O(n2)。 另外20pts:得分即为1+2+3+…+n。 100pts:在dfs求出以某一节点为根后,再次使用dfs进行换根操作。如果以u为根的答案已知为ans,则如果以u的某一子节点v为根,答案即为ans+n-2*size[v]。
Problem.C
公路沿线有n个城市,可以用一条直线表示。第i个城市位于距起点ai公里处。所有的城市都位于同一个方向。有m辆卡车从一个城市驶向另一个城市。
每辆卡车可以用4个整数来描述:起始城市si、结束城市fi、油耗ci和允许的加油次数ri。第i辆卡车每公里耗油量为ci升。
当卡车到达某个城市时,它可以加油。第i辆卡车最多可以加油ri次。每次加油都会把卡车的油箱加满。一开始,所有卡车的油箱都是满的。
所有卡车都将配备相同的V升油箱。请找出最小可能的V,使所有卡车都能在加油不超过规定次数的前提下到达目的地。
第一行包含两个整数n和m(2≤n≤400,1≤m≤250,000)代表城市数和卡车数。
第二行包含n个整数a1,a2,…an(1≤ai≤109,ai<ai+1)升序表示城市的位置。
接下来m行,每行4个整数。第i行包含整数si,fi,ci,ri (1≤si<fi≤n, 1≤ci≤109, 0≤ri≤n)描述了第i辆卡车。
输出一行一个整数表示最小可能的油箱大小V,满足所有的卡车都能到达目的地。
Example
input
7 6
2 5 7 10 14 15 17
1 3 10 0
1 7 12 7
4 5 13 3
4 7 10 1
4 7 10 1
1 5 11 2
output
55
Note
我们从细节考虑问题:
1. 第一辆卡车需要在不加油的条件下从2到达7,所以它需要的油箱大小最小是50。
2. 第二辆卡车需要从2到达17,且在任意城市都可以加油,所以它需要的油箱大小最小是48。
3. 第三辆卡车需要从10到达14,且在此之间没有任何城市,所以它需要的油箱大小最小是52。
4. 第四辆卡车需要从10到达17且只能加油一次。最优的方案是在城市5(位置14)加油,所以它需要的油箱大小最小是40。
5. 第五辆卡车情况与第四辆相同,所以它需要的油箱大小最小是40。
6. 第四辆卡车需要从2到达14且只能加油两次。第一次在城市2或3,第二次在城市4,所以它需要的油箱大小最小是40。
Hint
对于30%的数据,满足n≤80
对于另外30%的数据,满足m≤10,000
标程:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=402;
int n,m;ll ans;
int dp[maxn][maxn][maxn],a[maxn];
int s,f,r;ll c;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++){
for(int j=i;j<=n;j++){
dp[i][j][0]=a[j]-a[i];
}
}
for(int k=1;k<=n;k++){
for(int i=1;i<=n;i++){
int w=i;
for(int j=i;j<=n;j++){
while(w<j&&max(dp[i][w][k-1],a[j]-a[w])>max(dp[i][w+1][k-1],a[j]-a[w+1])) ++w;
dp[i][j][k]=max(dp[i][w][k-1],a[j]-a[w]);
}
}
}
while(m--){
scanf("%d%d%lld%d",&s,&f,&c,&r);
ans=max(ans,dp[s][f][r]*c);
}
printf("%lld\n",ans);
return 0;
}
30pts:设 dp{i,j,k}为:从第 i 个城市到第 j 个城市分成 k 段,这 k 段中长度最大的一段的最小值 状态转移方程:
dp {i,j,k}=min{ max(dp{i,w,k-1},aj-aw) } (0<k≤n)(i≤w≤j )
目标:max{ ci*dp{si,fi,ri}} i<=m
O(n^4)dp求解即可。
另外30pts:二分答案v,O(nm)判断是否可行。时间复杂度O(nmlog)
100pts:
考虑30分dp算法中有两个结果:
1)当 k,i 确定, j 在不断向右移时,对每个 j 取到的 w 具有单调性。
2)同时,当 j 确定时,不同的 w 对应的取值呈“先减后增” 的趋势。
我们先证明第二点,当i j k 确定时,转移方程中 我们设dp{i,w,k-1}为Aw,aj-aw为Bw,当w变大时,Aw可能变大,Bw必定变小,所以取值一开始肯定是取Bw的,慢慢变的有可能取Aw,可以想象,这个dp方程一开始肯定是w越大越好,但是当某一个临界点,如果比前面大了,那我们会发现,此时的最大值必定不是Bw,因为Bw<B(w-1),这是必定的。所以最大值是Aw,而w越大,Aw则可能变大,但绝不变小,所以不会有变小的趋势了,证毕。
然后证明第一点,假设j‘=j+1,我们先看w是否会前移。发现j变成j‘后,只有Bw会变大,Aw是不变的,如果w向前移动,则Bw变大,Aw可能变小,即此时移动一定是为了选取Aw的值,那么在j点也一定取了Aw的值,即若在j点w’取w-1会更优。所以不会前移。
那么看w是否会后移,由于Bw会变大,Aw只是可能变大,后面的项比前面的项更有可能用到Aw,所以后面的项可能更优,w可能后移,有单调性,证毕。