HGOI7.12集训题解

题解

讲真今天又是CF的题,而且还难得一匹…又和昨天一样爆零orz。我就是CF的搬运工(今天还是没有表情包orz)

第一题——环形山(crater)

【题目描述】

  • 给出n个区间,求出使得区间边界各不相交(不包括相切)的最大个数。
  • 原题:39C

  • 这个很明显是dp题。但是早上的思考方向不真确,局部最优解只拿了样例的分。自己的想法其实没什么营养所以就不讲了。接下来讲正解。
  • 第一眼就知道是区间dp,但是区间的范围有点大( 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 ] 表示节点的右边界,那么就进行区间当中的dp。复杂程度是 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,你要求给出一对数 g 0 , s 0 ,使得保留图中所有( g < g 0 , s < s 0 )使得图仍旧联通。你要求除所给的 w g g 0 + w s s 0 最小。

  • 这道题查了下好像是乌克兰国赛的第一题76A….我这种蒟蒻当然是打不出来的了orz。
  • 日常就是爆零。

  • 给出的是正解。因为自己根本想不出来。
  • 第一眼看出所得权值最小,又要把图联通,那就是最小生成树的板子。但双无关权值的最小值确实很难想。CF上的神仙给出的神奇代码只用了33行。由于有双权值,所以只好先根据第一权值 g 来进行第一次排序,然后再根据第二权值 v 来排序(其实可以用快排,但这种打打冒泡比较方便)
  • 每一次插入边之后做最小生成树。判断是否联通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;
}

猜你喜欢

转载自blog.csdn.net/qq_42037034/article/details/81020549