YMOI 2019.6.8

题解 YMOI 2019.6.8

前言

第二回考试,承让拿了第一次rank1,(●ˇ∀ˇ●)

题解

这次考试总体发挥比较好,每一道题都尽可能得取得了所能及的所有分。虽然多少还是有失误,不过在所难免。保持这种状态,继续努力。争取明年让某辣鸡跪着叫大佬┗|`O′|┛

T1 奆炮的重生

Vs9kS1.png

Vs9nTe.png

题干易懂,思路好想,良心题!

这个嘛,看一眼就能和某《石子合并》能联系上。如果说有什么难点,可能也就是存在负数,需要同时处理最大值和最小值吧

话是这么说的,实际得分\(60分\)真是狠狠打脸了..切记!想好范围再开数组想好范围再开数组想好范围再开数组

本来最有信心的一道题取到了三题分数的min

code:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;

const int MAX = 102;
const ll INF = 0x7fffffffffffffff;

int T, n;
ll ans;
ll f[MAX][MAX][2];

void work();

int main() {
    freopen("pao.in", "r", stdin);
    freopen("pao.out", "w", stdout);

    scanf("%d", &T);
    while (T--) work();

    return 0;
}

void work() {
    scanf("%d", &n);
    for (int i = 1; i <= (n << 1); ++i)
        for (int j = i; j <= (n << 1); ++j) f[i][j][0] = INF, f[i][j][1] = -INF;

    for (int i = 1; i <= n; ++i) scanf("%lld", &f[i][i][0]);
    for (int i = 1; i <= n; ++i) f[i + n][i + n][0] = f[i][i][0];
    for (int i = 1; i <= (n << 1); ++i) f[i][i][1] = f[i][i][0];

    for (int i = 1; i <= (n << 1) - 1; ++i)
        f[i][i + 1][0] = f[i][i][0] + f[i + 1][i + 1][0], f[i][i + 1][1] = f[i][i + 1][0];

    for (int i = (n << 1) - 2; i >= 1; --i) {
        for (int j = i + 2; j <= (n << 1); ++j) {
            for (int k = i; k <= j - 1; ++k) {  // i~k k+1~j
                f[i][j][1] = max(f[i][j][1], f[i][k][1] + f[k + 1][j][1]);
                f[i][j][0] = min(f[i][j][0], f[i][k][0] + f[k + 1][j][0]);
            }
            for (int m = i; m <= j - 2; ++m) {  // i~m m+1~n n+1~j
                for (int n = m + 1; n <= j - 1; ++n) {
                    f[i][j][1] = max(f[i][j][1], f[i][m][1] * f[n + 1][j][1] - f[m + 1][n][0]);
                    f[i][j][1] = max(f[i][j][1], f[i][m][0] * f[n + 1][j][1] - f[m + 1][n][0]);
                    f[i][j][1] = max(f[i][j][1], f[i][m][1] * f[n + 1][j][0] - f[m + 1][n][0]);
                    f[i][j][1] = max(f[i][j][1], f[i][m][0] * f[n + 1][j][0] - f[m + 1][n][0]);
                    f[i][j][0] = min(f[i][j][0], f[i][m][1] * f[n + 1][j][1] - f[m + 1][n][1]);
                    f[i][j][0] = min(f[i][j][0], f[i][m][0] * f[n + 1][j][1] - f[m + 1][n][1]);
                    f[i][j][0] = min(f[i][j][0], f[i][m][1] * f[n + 1][j][0] - f[m + 1][n][1]);
                    f[i][j][0] = min(f[i][j][0], f[i][m][0] * f[n + 1][j][0] - f[m + 1][n][1]);
                }
            }
        }
    }

    ans = -INF;
    for (int i = 1; i <= n; ++i) ans = max(ans, f[i][i + n - 1][1]);
    printf("%lld\n", ans);
}

T2 幽香的宴会

题目链接

吐槽!!什么鬼题面!!虽说题干是稍微有一点复杂,但也不至于代入世界观吧ㄟ( ▔, ▔ )ㄏ

扫描二维码关注公众号,回复: 11393297 查看本文章

当初看到这道题的时候就注意到了非强制在线,觉得有东西可搞,但是见识短浅,在考场的时候就是没能想出来

在考场上打了一个暴力,就是每一次都按照询问跑一遍BFS,然后用大根堆筛选前k个。没想到数据足够良心,竟然给了64分的暴力分

正解的想法用到了利用到了非强制在线。这道题有一点比较烦就是在不同情况下,图的形态是不确定的,这样导致我们每一次做都要推翻重来。假如我们事先把询问的降雨大小按照从高到低排序,就变成了向图里一点一点地加边。边加边变维护有效信息,整个操作只需要对图处理一遍,效率大大提高。

然后看看我们面临着怎么样的询问:

Q: 询问从c点开始,哪一片是互相连通的?

A: 这个嘛,并查集轻松解决

Q: 询问这些点的前k大值

A: 这个就涉及到了并查集的合并。每一个并查集都附赠一个小根堆来存某一篇区域的前k个值,利用这个小根堆去维护,转移即可。注意,小根堆的转移直接暴力倒出倒入即可,不需要其它优化

酱紫,就搞定了正解。

话说考试当天还真下雷阵雨了

code:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int MAX=2e5+5,INF=0x3f3f3f3f;

struct data1{
    int u,v; double w;

    bool operator < (data1 &x) const{
        return w>x.w;
    }
}edge[MAX];

struct data2{
    int c,p,id,ans;
}ask[MAX];

int n,m,t,k,c,p;
int dili[MAX];
int adding;
int fa[MAX],fa_size[MAX],fa_dep[MAX],fa_corn_sum[MAX];

priority_queue <int,vector<int>,greater<int> > fa_corn[MAX];

inline int read();
inline void insert(int,int,int,int);
void dijkstra(int,int);

int find(int);
void unionn(int,int);
bool check_in(int,int);
int check_size(int);
int check_corn(int);

bool cmpp(data2,data2);
bool cmpi(data2,data2);

int main(){
    #ifndef ONLINE_JUDGE
    freopen("test.in","r",stdin);
    #endif

    n=read(); m=read();
    for(int i=1;i<=n;++i) dili[i]=read();

    for(int i=1;i<=m;++i){
        int u,v; u=read(); v=read();
        double p; scanf("%lf",&p);
        edge[i].u=u; edge[i].v=v; edge[i].w=p;
    }

    t=read(); k=read();

    for(int i=1;i<=t;++i){
        int c,p; c=read(); p=read();
        ask[i].c=c; ask[i].p=p; ask[i].id=i;
    }

    sort(edge+1,edge+m+1);
    sort(ask+1,ask+t+1,cmpp);
    
    for(int i=1;i<=n;++i) fa[i]=i,fa_size[i]=1,fa_dep[i]=1,fa_corn[i].push(dili[i]);
    for(int i=1;i<=n;++i) fa_corn_sum[i]=dili[i];

    for(int k=1;k<=t;++k){
        while(adding<m&&edge[adding+1].w>=ask[k].p){
        	adding++;
        	unionn(edge[adding].u,edge[adding].v);
        }
        ask[k].ans=check_corn(ask[k].c);
    }

    sort(ask+1,ask+t+1,cmpi);

    for(int i=1;i<=t;++i) printf("%d\n",ask[i].ans);

    return 0;
}

inline int read(){
    char tmp=getchar(); int sum=0; bool flag=false;
    while(tmp<'0'||tmp>'9'){
        if(tmp=='-') flag=true;
        tmp=getchar();
    }
    while(tmp>='0'&&tmp<='9'){
        sum=(sum<<1)+(sum<<3)+tmp-'0';
        tmp=getchar();
    }
    return flag?-sum:sum;
}

int find(int x){
    if(x==fa[x]) return x;
    else return find(fa[x]);
}

void unionn(int a,int b){
    int fua=find(a);
    int fb=find(b);
    if(fua==fb) return;
    if(fa_dep[fua]<fa_dep[fb]) swap(fua,fb);

    if(fa_size[fua]+fa_size[fb]<=k){
        while(!fa_corn[fb].empty()){
            int tmp=fa_corn[fb].top(); fa_corn[fb].pop();
            fa_corn_sum[fua]+=tmp;
            fa_corn[fua].push(tmp);
        }
        fa_size[fua]+=fa_size[fb];
    }
    else{
        while(fa_corn[fua].size()+fa_corn[fb].size()>k){
            int tmpa=fa_corn[fua].top();
            int tmpb=fa_corn[fb].top();
            if(tmpa<tmpb){
                fa_corn[fua].pop();
                fa_corn_sum[fua]-=tmpa;
            }
            else{
                fa_corn[fb].pop();
                fa_corn_sum[fb]-=tmpb;
            }
        }
        fa_corn_sum[fua]+=fa_corn_sum[fb];
        while(!fa_corn[fb].empty()){
            fa_corn[fua].push(fa_corn[fb].top());
            fa_corn[fb].pop();
        }
        fa_size[fua]=k;
    }

    fa[fb]=fua;
    if(fa_dep[fua]==fa_dep[fb]) fa_dep[fua]++;

}

bool check_in(int a,int b){
    int fua=find(a);
    int fb=find(b);
    return fua==fb;
}

int check_size(int a){
    int fua=find(a);
    return fa_size[fua];
}

int check_corn(int a){
    int fua=find(a);
    return fa_corn_sum[fua];
}

bool cmpp(data2 a,data2 b){
	return a.p>b.p;
}

bool cmpi(data2 a,data2 b){
	return a.id<b.id;
}

T3 玉米的丰收

据说是某年NOI题的魔改,sjb大佬又来祸害人了(ノ`Д)ノ

VsP8Z8.png

VsPURs.png

题干真是相当新颖,没想到还有鬼才出题人想到点可以在边上,还是个能解决的问题

首先坦白,这道题我没有完全理解,我是用退火骗分才ac的。但是毕竟是道NOI题嘛,暂时先放一放

对于这道题的这个最优点,思考以下发现会有以下性质:

  1. 最优位置到至少两个点的距离等于最远距离【若只有一个点,则将最优位置向靠近这个点
    的方向移动会使最远距离变小】。

  2. 到最优位置距离等于最远距离的点中至少存在一对点在最优位置的两侧(最优位置在以这
    对点为两端的一条链的中点处)【若所有点都在同侧,则将最优位置向这一侧移动将使最
    远距离变小】。

因此最远距离等于图上某条链的中点上。由于是整数的一般,可能出现 \(\frac{1}{2}\) 的倍数。因此事先将所有点翻倍方便处理

VsFGCQ.md.png

然后还需要一点剪枝:

如果当前枚举的边 \(x \Leftrightarrow y\) 的距离 \(d[x][y] \geq ans\times 2\) 那么答案不可能更优,直接舍掉

如果当前枚举的边 \(x \Leftrightarrow y\) 的距离 \(d[x][y]\) (直线距离) \(> d[x][y]\) (最短距离),答案也不可能更优,画图即可理解

遍历 \(i\Leftrightarrow x\)\(i\Leftrightarrow y\) 的min,如果距离大于ans,直接排除即可。由于点的范围远远小于边的长度,因此剪枝有效

为边按照长度递减排一次序。sjb大佬指点的,感觉有道理

嗯,思路就是这样,还算理清了。

在考场的时候我用模拟退火玄学调了调,居然骗到了70分(。・∀・)ノ

事后,加上了正解里的剪枝,再配上liamaidi“分锅炖”的惊奇思路,在不断地调火温,总算是玄学地炖熟了!

code:

#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

struct data{
    int a,b; double w;

    bool operator < (const data &x) const{
        return w<x.w;
    }
}e[20005];

const int MAX=305,INF=0x3f3f3f3f;
const double INFF=21474836472147483647.0,eps=1e-10;

int n,m;
double d[MAX][MAX];
double ans=INFF;
clock_t st,ed;

int ecnt;

inline int read();
double simulatedannealing(int);
double distance(int,int,double,double);

int main(){
    #ifndef ONLINE_JUDGE
    freopen("test.in","r",stdin);
    #endif

    #ifdef ONLINE_JUDGE
    freopen("WA.in","r",stdin);
    freopen("WA.out","w",stdout);
    #endif

    n=read(); m=read();
    for(register int i=1;i<=n;++i) for(register int j=1;j<=n;++j) d[i][j]=INF;
    for(register int i=1;i<=m;++i){
        int a=read(),b=read(); double w; scanf("%lf",&w);
        d[a][b]=d[b][a]=w;
        ecnt++;
        e[ecnt].a=a; e[ecnt].b=b; e[ecnt].w=w;
    }

    for(register int k=1;k<=n;++k){
        for(register int i=1;i<=n;++i){
            for(register int j=1;j<=n;++j){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
    for(register int i=1;i<=n;++i) d[i][i]=0.0;

    sort(e+1,e+ecnt+1);

    for(register int k=1;k<=m;++k){
        if(e[k].w>d[e[k].a][e[k].b]) continue;
        if(e[k].w>ans*2) continue;
        for(register int i=1;i<=n;++i) if(max(d[e[k].a][i],d[e[k].b][i])>=ans) continue;
        ans=min(ans,simulatedannealing(k));
    }

    printf("%lf\n",ans);

    return 0;
}

inline int read(){
    char tmp=getchar(); int sum=0; bool flag=false;
    while(tmp<'0'||tmp>'9'){
        if(tmp=='-') flag=true;
        tmp=getchar();
    }
    while(tmp>='0'&&tmp<='9'){
        sum=(sum<<1)+(sum<<3)+tmp-'0';
        tmp=getchar();
    }
    return flag?-sum:sum;
}

double simulatedannealing(int k){
    double T=100.0,down=0.9;
    double x=e[k].w/2,ans=distance(e[k].a,e[k].b,x,e[k].w);

    while(T>eps){
        double nx=x+(rand()*2-RAND_MAX)/(double)RAND_MAX*e[k].w*T;
        while(nx>e[k].w||nx<0.0) nx=x+(rand()*2-RAND_MAX)/(double)RAND_MAX*e[k].w*T;
        double nans=distance(e[k].a,e[k].b,nx,e[k].w);

        if(nans<ans){
            ans=nans; x=nx;
        }
        else if(exp((ans-nans)/T)*RAND_MAX>rand()){
            ans=nans; x=nx;
        }

        T*=down;
    }

    if(m<1000) return ans;  // lia mai di you hua
    T=100.0;
    while(T>eps){
        double nx=x+(rand()*2-RAND_MAX)/(double)RAND_MAX*e[k].w*T;
        while(nx>e[k].w||nx<0.0) nx=x+(rand()*2-RAND_MAX)/(double)RAND_MAX*e[k].w*T;
        double nans=distance(e[k].a,e[k].b,nx,e[k].w);

        if(nans<ans){
            ans=nans; x=nx;
        }
        else if(exp((ans-nans)/T)*RAND_MAX>rand()){
            ans=nans; x=nx;
        }

        T*=down;
    }

    return ans;
}

double distance(int a,int b,double w,double len){
    double tmp=0.0;
    for(int i=1;i<=n;++i){
        tmp=max(tmp,min(d[i][a]+w,d[i][b]+len-w));
    }
    return tmp;
}

后记

第二次考试,感觉每一次考试都这么整理一下,肯定会累死受益匪浅

为了noip,继续努力

猜你喜欢

转载自www.cnblogs.com/ticmis/p/13210914.html