题解
讲真今天又是CF的题,而且还难得一匹…又和昨天一样爆零orz。我就是CF的搬运工(今天还是没有表情包orz)
第一题——环形山(crater)
【题目描述】
- 给出n个区间,求出使得区间边界各不相交(不包括相切)的最大个数。
- 原题:39C
- 这个很明显是dp题。但是早上的思考方向不真确,局部最优解只拿了样例的分。自己的想法其实没什么营养所以就不讲了。接下来讲正解。
- 第一眼就知道是区间dp,但是区间的范围有点大( ci,ri≤109 c i , r i ≤ 10 9 ),那就先进行离散化处理。
- 然后通过区间大小进行枚举左边界,进行区间dp。
- 方程如下。
if(a[j][1]<r){
if(dp[l][r]<dfs(l,a[j][1])+dfs(a[j][1],r)){
dp[l][r]=dp[l][a[j][1]]+dp[a[j][1]][r];
vis[l][r]=j;
}
}
- 解释下,j表示节点的编号, a[j][1] a [ j ] [ 1 ] 表示节点的右边界,那么就进行区间当中的dp。复杂程度是 O(n2) O ( n 2 ) 。
- 其实方向正确还是很好想的orz。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
void fff(){
freopen("crater.in","r",stdin);
freopen("crater.out","w",stdout);
}
const int MAXN=4110;
int m,n;
int h[MAXN],a[MAXN][2],id[MAXN][MAXN],dp[MAXN][MAXN],vis[MAXN][MAXN];
vector <int> g[MAXN];
int dfs(int l,int r){
if(l>=r) return dp[l][r]=0;
if(~dp[l][r]) return dp[l][r];
dp[l][r]=dfs(l+1,r);
for (int i=0;i<g[l].size();i++){
int j=g[l][i];
if(a[j][1]<r){
if(dp[l][r]<dfs(l,a[j][1])+dfs(a[j][1],r)){
dp[l][r]=dp[l][a[j][1]]+dp[a[j][1]][r];
vis[l][r]=j;
}
}
}
return dp[l][r]+=(id[l][r]?1:0);
}
void output(int l,int r){
if(l>r) return;
if(id[l][r]) printf("%d ",id[l][r]);
if(vis[l][r]){
output(l,a[vis[l][r]][1]);
output(a[vis[l][r]][1],r);
}else{
output(l+1,r);
}
}
int main(){
fff();
m=0;
scanf("%d",&n);
for (int i=1;i<=n;i++){
int x,r;
scanf("%d%d",&x,&r);
h[m++]=a[i][0]=x-r,h[m++]=a[i][1]=x+r;
}
sort(h,h+m);
m=unique(h,h+m)-h;
for (int i=1;i<=n;i++){
a[i][0]=lower_bound(h,h+m,a[i][0])-h+1;
a[i][1]=lower_bound(h,h+m,a[i][1])-h+1;
g[a[i][0]].push_back(i);
id[a[i][0]][a[i][1]]=i;
}
memset(dp,-1,sizeof(dp));
printf("%d\n",dfs(1,m));
output(1,m);
}
第二题——国王的礼物(gift)
【题目描述】
- 给定n个点和m条边,每条边包括属性(g,s)。你被给定wg和ws,你要求给出一对数 (g0,s0) ( g 0 , s 0 ) ,使得保留图中所有( g<g0,s<s0 g < g 0 , s < s 0 )使得图仍旧联通。你要求除所给的 wg∗g0+ws∗s0 w g ∗ g 0 + w s ∗ s 0 最小。
- 这道题查了下好像是乌克兰国赛的第一题76A….我这种蒟蒻当然是打不出来的了orz。
- 日常就是爆零。
- 给出的是正解。因为自己根本想不出来。
- 第一眼看出所得权值最小,又要把图联通,那就是最小生成树的板子。但双无关权值的最小值确实很难想。CF上的神仙给出的神奇代码只用了33行。由于有双权值,所以只好先根据第一权值 g g 来进行第一次排序,然后再根据第二权值来排序(其实可以用快排,但这种打打冒泡比较方便)
- 每一次插入边之后做最小生成树。判断是否联通orz…其实就是个沙雕贪心+更优选择
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
typedef long long LL;
void fff(){
freopen("gift.in","r",stdin);
freopen("gift.out","w",stdout);
}
const int MAXN=211;
struct Edge{
int from,to;
LL vg,vs;
bool operator <(const Edge x) const {
return vg<x.vg;
}
} q[250];
vector <Edge> edge;
LL n,m,tG,tS,s,fa[MAXN];
LL find_(LL x){
if(x!=fa[x]) fa[x]=find_(fa[x]);
return fa[x];
}
int main(){
fff();
scanf("%d%d",&n,&m);
scanf("%d%d",&tG,&tS);
for (int i=1;i<=m;i++){
int x,y,a,b;
scanf("%d%d%d%d",&x,&y,&a,&b);
edge.push_back((Edge){x,y,a,b});
}
sort(edge.begin(),edge.end());
LL ans=0x7fffffffffffffff;
for (LL i=0,t=0;i<edge.size();i++){
Edge e=edge[i];
q[++t]=e;
s=0;
for (LL j=t-1;j&& q[j+1].vs < q[j].vs;j--)
swap( q[j],q[j+1] );
for (LL j=1;j<=n;j++) fa[j]=j;
for (LL j=1,x,y;s<n-1&&j<=t;j++){
if((x=find_(q[j].from))!=(y=find_(q[j].to))) fa[x]=y,q[++s]=q[j];
}
if(s==n-1) ans=min(ans,1LL*tG*edge[i].vg+1LL*tS*q[s].vs);
t=s;
}
cout<<(ans==0x7fffffffffffffff? -1:ans);
return 0;
}
第三题——回文子串(palind)
【题目描述】
- 给你一个字符串,求出所有不相交的回文串的对数。
- cf的古董题17E
- 由于相交的有点难求,那就只好求不相交的。而回文串当中的子串也是回文串,那么我们可以利用manacher求出以i为中心的回文串的最大半径r[i],从i之前r[i]的位置开始到i所有为头的+1,i到i+r[i]的位置全部-1。再采用另外一个数组d[i]表示从i之后开始的回文串的总个数,乘以i之前结尾的回文串的总个数就是不相交的个数。
- 而求这一个不相交的个数,则只需要利用差分法就可以进行求取,最后的d[i]就只要递推求得就可以了。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
void fff(){
freopen("palind.in","r",stdin);
freopen("palind.out","w",stdout);
}
const int MAXN=4000005;
const int MOD=51123987;
long long n,l,a[MAXN],f[MAXN],g[MAXN],mxr=0,p,sum=0;
char s[MAXN];
int main(){
// fff();
s[0]='@';
scanf("%d",&l);
getchar();
for (int i=1;i<=l;i++)
{
s[i*2-1]='#';
s[i*2]=getchar();
}
s[l*2+1]='#';
s[l*2+2]='?';
n=l*2+1;
for (int i=1,q;i<=n;i++)
{
if (mxr>i) q=min(mxr-i,a[p*2-i]);
else q=1;
while (s[i-q]==s[i+q]) q++;
a[i]=q;
sum+=(a[i]-1)/2;
if (i%2==0) sum++;
sum%=MOD;
if (i+q>mxr) mxr=i+q,p=i;
}
////////////////////// manacher
sum=sum*(sum-1)/2;
for (int i=2;i<=n;i+=2){
f[i-a[i]+2]++;
f[i+2]--;
g[i]++;
g[i+a[i]]--;
}
for (int i=1;i<=n;i+=2){
f[i-a[i]+2]++;
f[i+1]--;
g[i+1]++;
g[i+a[i]]--;
}
/////////////差分
for (int i=2;i<=n;i+=2){
f[i]+=f[i-2];
f[i]%=MOD;
g[i]+=g[i-2];
g[i]%=MOD;
}
f[n+1]=0;
//////////递推求取i之后开始的f[i]
for (int i=n-1;i>=1;i-=2){
f[i]+=f[i+2];
f[i]%=MOD;
}
for (int i=2;i<=n;i+=2){
sum-=g[i]*f[i+2]%MOD;
sum=(sum+MOD)%MOD;
}
cout<<(sum%MOD+MOD)%MOD;
return 0;
}