HGOI7.11集训题解

题解

首先声明:由于今天一道题都没有做出来…第一题只骗了30分(真的是题目太难…orz)所以以下的题目标程都是大佬提供的…


第一题——国王的难题(king)

【题目描述】

  • 有n+1个点,其中n个点在x轴上,另一个点单独拎出来,从第k个点出发,求这些点的哈密顿回路。

  • 这道题真的是扎心啊..大清早打了两个小时的分类讨论…(真的以为是数学题..)但是很不幸的是我打错了orz,所想到的并不是最优解… 然后就爆零了…,但其实这道题真的蛮难的。是codeforce上的30D..

  • 正解是这样子的啊..还是分类讨论。

  • 先要进行整体的处理啊..将前面的几个点排一遍,获得序列 a 1 . . a n
  • 首先有一种情况,就是起点是在那一个单独的点p。那就是可以证明回路长度是:

    d i s t ( a 1 , a n ) + m i n ( d i s t ( a 1 , p ) , d i s t ( a n , p ) )

  • 第二种就是起点不在那个单独的p上,那么可以证明x轴上存在点k使得人从1~k走再回来,再往k+1~n,或者k+1~n走再回来,再往1~k。
  • 然后就要考虑,从p点出发,走进一个区间 [ l , r ] ,最佳方案就是从p出发走到 l , r 当中较近的一个,然后再走到p,距离就是 d i s t ( a l , a r ) + m i n ( d i s t ( a l , p ) . d i s t ( a r , p ) )
  • 从k点出发,走一个区间 [ l , r ] 在来到p,距离就是 a b s ( a l a r ) + m i n ( a b s ( a k a l ) + d i s t ( r ) , a b s ( a k a l ) + d i s t ( l ) )
  • 然后就是枚举k计算出答案。复杂度是 O ( n )

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100100
using namespace std;
int n,k;
double newtag;
double xx[N];
double ans;
double y;
double get_dis(int l)
{
    return sqrt((xx[l]-xx[n+1])*(xx[l]-xx[n+1])+y*y);
}
double dis[N];
double to[N];
double ca1(int l,int r)
{
    return xx[r]-xx[l]+min(get_dis(l),get_dis(r));
}
double ca2(int l,int r)
{
    return xx[r]-xx[l]+min(get_dis(l)+abs(newtag-xx[r]),get_dis(r)+abs(newtag-xx[l]));
}
int main()
{
//  freopen("king.in", "r", stdin);
//  freopen("king.out", "w", stdout);
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n+1;i++)
    {
        int x;
        scanf("%lf",&xx[i]);
    }
    scanf("%lf",&y);
    newtag=xx[k];
    sort(xx+1,xx+n+1);
    if(k==n+1)
    {
        double ans=ca1(1,n);
        printf("%.10lf\n",ans);
    }else
    {
        double ans=ca2(1,n);
        for(int i=1;i<n;i++)
        {
            ans=min(ans,min(ca1(1,i)+ca2(i+1,n),ca2(1,i)+ca1(i+1,n)));
        }
        printf("%.10lf\n",ans);
    }

    return 0;
}

第二题——别怕,DravDe很善良(dravde)

【题目描述】

  • 给定一个四元组(v,c,l,r),要求出一个子序列使得满足:
  • 子序列当中的c+l+r相等。
  • 第一个元素l=0,最后一个元素r=0。
  • 第i个元素的l等于前所有个元素的c之和
  • 整体子序列的v之和尽量的大。

  • 真的第一题打完之后我就没心情来打这道题了..而且还是这么恶心的dp。查了下,是CF上的28D。怕教练是在CF上乱扒题的orz。
  • 看大佬的神仙代码我真的没有想到这么短..但确实很难理解orz。讲下算法。
  • 由于对于当前的车辆需要前面和后面的人数是固定的,所以需要筛出这些点了。
  • 利用map求出对于固定的l之后的r+c的点,那么当前这给点就是c所需要的,对应的之前的点的附加值。然后在更新map当中l所对应的r+c的值(求最大orz)。
  • 这个代码真的是神仙。
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
#include <map>
using namespace std;
void fff(){
    freopen("dravde.in","r",stdin);
    freopen("dravde.out","w",stdout);
}
const int MAXN=100001;
int n;
map<int,int> m[MAXN];
int f[MAXN],s[MAXN],p[MAXN],cnt=0,z;
int main(){
//  fff();
    scanf("%d",&n);
    for (int i=1;i<=n;i++){
        int v,c,l,r;
        scanf("%d%d%d%d",&v,&c,&l,&r);
        if(r+c<MAXN&&(!l||m[l].find(r+c)!=m[l].end())){
            int u=m[l][r+c];
            f[i]=f[u]+v,p[i]=u;
            if(!r&&f[i]>f[z]){
                z=i;
            }
            if(f[i]>f[m[l+c][r]]){
                m[l+c][r]=i;
            }
        }
    }
    while (z){
        s[cnt++]=z;
        z=p[z];
    }
    printf("%d\n",cnt);
    while (cnt--){
        printf("%d ",s[cnt]);
    }
    return 0;
}

第三题——复杂易记的密码(tricky)

【题目描述】

  • 给定字符串S,求一个奇数位的回文串T使得:

S = S 1 + T [ 1 , x ] + S 2 + T [ x + 1 , n x ] + S 3 + T [ n x + 1 , n ]

  • S1,S2,S3为任意串,可以为空串。求满足条件的字符串长度最大为多少。

  • 这也是一道CF的题orz。但应该是所有题目当中最简单的一道了。30E
  • 其实思想是比较好想的,在考试的时候已经想的差不多了但时间不够orz。
  • 可以把字符串翻过来,那么回文串的头就有了,然后与翻转前的进行比较,最大程度进行匹配。剩下的中间部分就是可以用马拉车进行最大的回文串半径求得。
/**
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int n,i,j,k,f[100011],g[100011],dl[100011],m[100011],s,e,mid,ans;
unsigned hl[100011],hr[100011],pow[100011];
char c[100011];

unsigned hash(int s,int e)
{
         if (s<e) return hl[e]-hl[s]*pow[e-s];
         else     return hr[e]-hr[s]*pow[s-e];
}

int calc(int x)
{
    int s=x-f[x]+1,e=x+f[x]-1;
    return f[x]*2-1+min(g[m[s-1]],n-e)*2;
}

int main()
{
    freopen("tricky.in", "r", stdin);
    freopen("tricky.out", "w", stdout);
    scanf("%s",c+1);
    n=strlen(c+1);
    for (i=1;i<=n;i++) hl[i]=hl[i-1]*29+c[i];
    for (i=n;i>=1;i--) hr[i]=hr[i+1]*29+c[i];
    pow[0]=1;
    for (i=1;i<=n;i++) pow[i]=pow[i-1]*29;
    for (i=1;i<=n;i++)
    {
        s=1;
        e=min(i,n-i+1);
        while (s!=e)
        {
              mid=s+e-(s+e)/2;
              if (hash(i,i+mid-1)==hash(i,i-mid+1)) s=mid;
              else                                  e=mid-1;
        }
        f[i]=s;
    }
    s=1;
    e=0;
    for (i=1;i<=n;i++)
    {
        if (c[i]==c[n]) dl[++e]=i;
        while (s<=e && hash(dl[s],i)!=hash(n,n-(i-dl[s])) && n-(i-dl[s])>i) s++;
        if (s>e) g[i]=0;
        else     g[i]=i-dl[s]+1;
    }
    for (i=1;i<=n;i++)
    {
        m[i]=m[i-1];
        if (g[m[i]]<g[i]) m[i]=i;
    }
    for (i=1;i<=n;i++)
        ans=max(ans,calc(i));
    for (i=1;i<=n;i++)
        if (ans==calc(i))
        {
           s=i-f[i]+1;
           e=i+f[i]-1;
           j=min(g[m[s-1]],n-e);
           k=m[s-1]-g[m[s-1]]+1;
           if (j) cout << 3 << endl 
                       << k << " " << j << endl 
                       << s << " " <<e-s+1 << endl
                       << n-j+1 << " " << j << endl;
           else cout << 1 << endl << s << " " << e-s+1 << endl;
           return 0;
        }
    return 0;
}
  • 程序当中更加的暴力 orz,直接求出串所对应的hash来求出中间的回文串orz。
  • 首先求下以每个字符为中心的最长回文串的长度f[i]
  • 然后发现suffix的后面没有加东西,那么我们可以对每个位置求以这个位置结尾最长能多长跟最后匹配,记为g[i]
  • 然后再定义m[i],满足g[m[i]]=max(g[1]..g[i])
  • 然后对于i作为middle的中心,他能形成的最长原串长度为f[i]*2-1+2*min(g[m[i-f[i]]],n-(i+f[i]-1))

猜你喜欢

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