题解小合集——第八弹

(转载于我的洛谷博客

索引:

第一题:P2564 生日礼物

第二题:P3084 照片

第三题:P4878 布局

第四题:P2736 “破锣摇滚”乐队

第五题:P4568 飞行路线

第六题:P1284 三角形牧场

第七题:P3959 宝藏

第八题:P1197 星球大战

第九题:P1337 平衡点

第十题:P1325 雷达安装

第一题:P2564 生日礼物

题解思路:单调队列(尺取法)

这题和P1638 逛画展很像,唯一的区别就是可能同一个位置上可能都有多个"彩珠"

然而有多个"彩珠"这点有和P2698 花盆这题一样

所以我们可以继承两题的思路,用P1638的单调队列和P2698里的结构体来解决这个题。显然,我们的结构体要记录两个数据:位置,种类。然后我们对位置排序,以便于维护单调序列。

我们用一个指针l指向区间的最左端的珠子,然后遍历所有珠子(不用去循环右端点,因为在彩带上会有地方没有珠子,把右端点放在没有珠子的地方是没有意义的),每增加一颗i种类的珠子,kind[i]就记录i种类的珠子的最近出现的位置。如果又找到了一颗和 区间最左端的珠子 相同的珠子,那么最左端的珠子就没有存在的必要了,把它出队即可。当区间包含所有种类的珠子时,我们更新并记录最优答案,在遍历完之后输出即可。

请结合代码加深理解……

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct Node
{
    int x,y;
}a[1000010];
int n,m,ans=0x3f3f3f3f,cnt,k[65];
bool cmp(const Node &a,const Node &b)
{
    return a.x<b.x;
}
int main()
{
    memset(k,-1,sizeof(k));
    scanf("%d%d",&n,&m);
    int b,c;
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&b);
        for(int j=1;j<=b;j++)
        {
            scanf("%d",&c);
            a[++cnt].x=c;
            a[cnt].y=i;
        }
    }
    sort(a+1,a+n+1,cmp);
    int l=1;cnt=0;
    for(int i=1;i<=n;i++)
    {
        if(k[a[i].y]==-1)cnt++;
        k[a[i].y]=a[i].x;
        while(l<=i&&a[l].x!=k[a[l].y])l++;
        if(cnt==m&&a[i].x-a[l].x<ans)ans=a[i].x-a[l].x;
    }
    printf("%d",ans);
    return 0;
}

第二题:P3084 照片

题解思路:差分约束系统+最短路

我觉得这个题和Intervals有相似之处(题解小合集——第六弹第六题)

我们在利用差分约束系统解题的过程中一定要关注题目的设问,若求的是两个变量差的最大值,那么将所有不等式转变成"<="的形式并且在建图后求最短路;反之则转换成">="的形式,并且求最长路

显然,本题问的是“最多可能有多少只斑点奶牛”,所以我们一定要把不等式都化成\(a-b<=c\)的形式,然后建边\((b,a)=c\),然后跑最短路。

不过,USACO的题日常卡spfa,只是这样做是过不了了,我们要用双端队列优化spfa

我们在原来的spfa中会这样:

if(!vis[v])
{
    vis[v]=1;
    q.push(v);
}

双端队列优化后就成了这样:

if(q.size()&&dis[q.front()]<dis[v])q.push_back(v);
else q.push_front(v);

意义很显然,当队首元素大于等于当前节点的dis,我们就把他放入队首,下次先扩展

但是加了这个优化以后才90分,这又是咋回事呢?

我们记录所有节点的总入队次数,如果过大就直接return

最后别忘了判环

以下是AC代码:

#include<deque>
#include<cstdio>
#include<iostream>
#define reg register
using namespace std;
struct Edge
{
    int nst,to,dis;
}edge[800010],ddd;
int head[400010],dis[400010],cnt,n,m,num[400010],tot;
bool vis[400010];
inline void add(int a,int b,int c)
{
    edge[++cnt].nst=head[a];
    edge[cnt].to=b;
    edge[cnt].dis=c;
    head[a]=cnt;
}
inline bool spfa()
{
    deque <int> q;
    for(int i=1;i<=n;i++)
    {
        dis[i]=0x3f3f3f3f;
        vis[i]=0;
    }
    dis[0]=0;
    vis[0]=1;
    q.push_back(0);
    while(!q.empty())
    {
        int u=q.front();
        q.pop_front();
        vis[u]=0;
        num[u]++;
        if(num[u]>=n)return 1;
        for(int i=head[u];i;i=edge[i].nst)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].dis)
            {
                dis[v]=dis[u]+edge[i].dis;
                if(!vis[v])
                {
                    tot++;
                    
                    if(q.size()&&dis[q.front()]<dis[v])q.push_back(v);
                    else q.push_front(v);
                    vis[v]=1;
                }
                if(tot>2003212)return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d",&n,&m);
    int a,b;
    for(reg int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        add(a-1,b,1);
        add(b,a-1,-1);
    }
    for(reg int i=1;i<=n;i++)
    add(i-1,i,1),add(i,i-1,0);
    if(spfa()){printf("-1");return 0;}
    printf("%d",dis[n]);
    return 0;
}

第三题:P4878 布局

题解思路:差分约束

这个题和上面的第二题很像,但是有一点不同——上面的题不需要超级源点,因为它所有的点都是相互连通的(每两个点之间建了双向边),而且建图时需要把左端点减一,因为这样\(f[b]-f[a-1]\)才是区间\([a,b]\)上斑点奶牛的数量。

但是这个题却不用,因为这个题里的\(f[i]\)表示的是从奶牛1到奶牛i的距离,所以\(f[i]-f[1]\)即为1-i的距离。而且,由于此题中两点间只建了单向边,所以无法确保图的连通性,所以需要0作为超级源点来判断连通性。故此题的思路是先跑一遍spfa(0)判连通再跑一遍spfa(1)求距离

以下是AC代码:

#include<deque>
#include<cstdio>
#include<iostream>
#define reg register
using namespace std;
struct Edge
{
    int nst,to,dis;
}edge[800010],ddd;
int head[400010],dis[400010],cnt,n,m,k,num[400010],tot;
bool vis[400010];
inline void add(int a,int b,int c)
{
    edge[++cnt].nst=head[a];
    edge[cnt].to=b;
    edge[cnt].dis=c;
    head[a]=cnt;
}
inline bool spfa(int s)
{
    deque <int> q;
    for(int i=1;i<=n;i++)
    {
        dis[i]=0x3f3f3f3f;
        vis[i]=0;
    }
    dis[s]=0;
    vis[s]=1;
    q.push_back(s);
    while(!q.empty())
    {
        int u=q.front();
        q.pop_front();
        vis[u]=0;
        num[u]++;
        if(num[u]>=n)return 1;
        for(int i=head[u];i;i=edge[i].nst)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].dis)
            {
                dis[v]=dis[u]+edge[i].dis;
                if(!vis[v])
                {
                    tot++;
                    
                    if(q.size()&&dis[q.front()]<dis[v])q.push_back(v);
                    else q.push_front(v);
                    vis[v]=1;
                }
                if(tot>2003212)return 1;
            }
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    int a,b,c;
    for(reg int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(a,b,c);
    }
    for(reg int i=1;i<=k;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        add(b,a,-c);
    }
    for(reg int i=1;i<=n;i++)
    add(i+1,i,0);
    for(reg int i=1;i<=n;i++)
    add(0,i,0);
    if(spfa(0)){printf("-1");return 0;}
    spfa(1);
    if(dis[n]>=0x3f3f3f3f){printf("-2");return 0;}
    else printf("%d",dis[n]);
    return 0;
}

第四题:P2736 “破锣摇滚”乐队

题解思路:二维费用的背包问题

关于二维费用的背包问题详见背包九讲

咳咳,对于这个题,也不是裸的二维费用板子,可以进行优化和改进的。我们用\(f[i][j]\)表示目前用了i张光盘,最后一张光盘还剩j分钟的空间时的最大录制歌曲数。那么对于每一首歌一共有三种状态:

  1. 不选这首歌
  2. 在当前光盘里存这首歌
  3. 在一张新光盘里存这首歌

故状态转移方程为:\(f[i][j]=max(f[i][j],f[i-1][t]+1,f[i][j-a[k]]+1)\),k是歌曲序号

还有一点需要注意,我们把光盘和歌曲时长看成二维费用,由于是01背包的变形,所以循环这两个变量时要按倒序

以下是AC代码:

#include<cstdio>
#include<iostream>
using namespace std;
int n,m,t,a[25];
int f[25][25];
int maxx(int a,int b,int c)
{
    a=max(a,b);
    a=max(a,c);
    return a;
}
int main()
{
    scanf("%d%d%d",&n,&t,&m);
    for(int i=1;i<=n;i++)
    scanf("%d",&a[i]);
    for(int i=1;i<=n;i++)
        for(int j=m;j>=1;j--)
            for(int k=t;k>=a[i];k--)
            f[j][k]=maxx(f[j][k],f[j-1][t]+1,f[j][k-a[i]]+1);
    printf("%d",f[m][t]);
    return 0;
}

第五题:P4568 飞行路线

题解思路:多层图

这个是多层图板子题……

我们在各层内部正常连边,各层之间从上到下连权值为0的边。每向下跑一层,就相当于免费搭一次飞机。

当然,由于某些毒瘤出题人会设置一些不用坐k次免费飞机就能过的数据,所以我们在每层图的终点之间连边权为0的边,最后跑\(s\)->\(t+k*n\)的最短路就好了

以下是AC代码:

#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
struct Edge
{
    int nst,to,dis;
}edge[2000010];
int head[110010],dis[110010],n,m,k,cnt,s,t;
bool vis[110010];
inline int read()
{
    int fu=1,x=0;char o=getchar();
    while(o<'0'||o>'9'){if(o=='-')fu=-1;o=getchar();}
    while(o>='0'&&o<='9'){x=(x<<1)+(x<<3)+(o^48);o=getchar();}
    return x*fu;
}
inline void add(int a,int b,int c)
{
    edge[++cnt].nst=head[a];
    edge[cnt].to=b;
    edge[cnt].dis=c;
    head[a]=cnt;
}
void dijkstra()
{
    priority_queue <pair<int,int> > q;
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    q.push(make_pair(0,s));
    while(q.size())
    {
        int u=q.top().second;
        q.pop();
        if(vis[u])continue;
        vis[u]=1;
        for(int i=head[u];i;i=edge[i].nst)
        {
            int v=edge[i].to;
            if(dis[v]>dis[u]+edge[i].dis)
            {
                dis[v]=dis[u]+edge[i].dis;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}
int main()
{
    n=read();m=read();k=read();
    int a,b,c;
    s=read();t=read();
    for(int i=1;i<=m;i++)
    {
        a=read();b=read();c=read();
        add(a,b,c);
        add(b,a,c);
        for(int j=1;j<=k;j++)
        {
            add(a+(j-1)*n,b+j*n,0);
            add(b+(j-1)*n,a+j*n,0);
            add(a+j*n,b+j*n,c);
            add(b+j*n,a+j*n,c);
        }
    }
    for(int i=1;i<=k;i++)
    add(t+(i-1)*n,t+i*n,0);
    dijkstra();
    printf("%d",dis[t+k*n]);
    return 0;
}

第六题:P1284 三角形牧场

题解思路:DP(正解)或随机化贪心(骗分)

DP正解

这个题的DP写起来比较麻烦,所以我们可以通过其他的方式来解这个题,比如随机化贪心

我们知道,贪心每次求出的都是较优解,所以我们可以通过打乱木板数组的顺序来进行贪心,记录每一次的较优解,最优解很可能就在其中了

以下是AC代码:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define re register
using namespace std;
int n,x[45],a[3];
int ans=-1;
int clac(double a,double b,double c)
{
    if(a+b>c&&a+c>b&&b+c>a)
    {
        double p=(a+b+c)/2;
        return trunc(sqrt(p*(p-a)*(p-b)*(p-c))*100);
    }
    else return -1;
}
void work()
{
    a[0]=x[1],a[1]=x[2],a[2]=x[3];
    for(int i=4;i<=n;i++)
    {
        int tmp=0x3f3f3f3f,id;
        for(int j=0;j<=2;j++)if(tmp>a[j])tmp=a[j],id=j;
        a[id]+=x[i];
    }
    ans=max(ans,clac(a[0],a[1],a[2]));
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    scanf("%d",&x[i]);
    for(int i=1;i<=500000;i++)
    {
        random_shuffle(x+1,x+1+n);
        work();
    }
    printf("%d",ans);
    return 0;
}

第七题:P3959 宝藏

题解思路:DFS(正解)或模拟退火

正解在这里

我的DFS从40分改到了70分,但巨佬们说的玄学剪枝我看不懂,所以AC难度就比较大

顺便一说,把DFS从40分改到70分只需一句话:

dis[i][j]=dis[j][i]=min(dis[i][j],c);

因为“对于70%的数据,\(1≤n≤8,0≤m≤1000,v≤5000\)”,显然整个图内最多有64条边,m却有1000条,所以一定有重边,我们每次选择重边中最小的一条就好了

这是我的70分代码:

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int inf=0x3f3f3f3f;
int e[20][20],n,m,dis[20][20],tmp,l[20],ans=inf;
inline void dfs(int e,int num)
{
    if(n==num)
    {       
        ans=min(ans,tmp);
        return;
    }
    if(tmp>=ans)return;
    for(int i=1;i<=n;i++)
    {
        if(l[i])continue;
        for(int j=1;j<=n;j++)
        {
            if(dis[j][i]==inf||!l[j]||i==j)continue;
            tmp+=l[j]*dis[j][i];
            l[i]=l[j]+1;
            dfs(i,num+1);
            tmp-=l[j]*dis[j][i];
            l[i]=0;
        }
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    int a,b,c;
    memset(dis,63,sizeof(dis));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        dis[a][b]=dis[b][a]=min(dis[a][b],c);
    }
    for(int i=1;i<=n;i++)
    {
        l[i]=1;dfs(i,1);l[i]=0;
    }
    printf("%d",ans);
    return 0;
}

接下来说模拟退火(其实严格来讲并不是模拟退火,而是随机化prim)

虽然这个题prim已经被证明了是不正确的,但是我们可以通过随机化,让prim有一定概率去找距离更远的点,就有可能找到最优解。而且该算法的复杂度并不高,我们跑1000遍随机化prim,总有一次能找到最优解(毕竟n这么小)

以下是 看脸 AC代码:

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
using namespace std;
int n,m,e[13][13],dep[13];
const int inf=0x3f3f3f3f;
struct Edge
{
    int u,v;
};
bool operator < (struct Edge a,struct Edge b)
{
    return dep[a.u]*e[a.u][a.v]>dep[b.u]*e[b.u][b.v];
}
int find(int s)
{
    memset(dep,0,sizeof(dep));
    int vis[13]={0};
    priority_queue <Edge> heap;
    Edge past[1000];
    int p=0;
    Edge a,b;
    int cost=0;
    dep[s]=1;
    vis[s]=1;
    for(int i=1;i<=n;i++)
    if(e[s][i]<inf)
    {
        a.u=s;
        a.v=i;
        heap.push(a);
    }
    for(int i=1;i<n;i++)
    {
        a=heap.top();heap.pop();
        while(!heap.empty()&&((vis[a.v]||rand()%(n)<1)))
        {
            if(!vis[a.v])past[p++]=a;
            a=heap.top();
            heap.pop();
        }
        vis[a.v]=1;
        dep[a.v]=dep[a.u]+1;
        if(p-->0)
        {
            for(;p>=0;p--)heap.push(past[p]);
        }
        p=0;
        for(int i=1;i<=n;i++)
        if(e[a.v][i]<inf&&!vis[i])
        {
            b.u=a.v;b.v=i;
            heap.push(b);
        }
        cost+=e[a.u][a.v]*dep[a.u];
    }
    return cost;
}
int main()
{
    scanf("%d%d",&n,&m);
    int a,b,c;
    memset(e,63,sizeof(e));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d%d",&a,&b,&c);
        e[a][b]=e[b][a]=min(e[a][b],c);
    }
    srand(19260817);
    int minn=inf;
    for(int j=1;j<1000;j++)
    for(int i=1;i<=n;i++)
    minn=min(minn,find(i));
    printf("%d",minn);
    return 0;
}

第八题:P1197 星球大战

题解思路:并查集

我们发现,对于这个题,要把一个点从图中割去真是难了去了,但是如果是向图中添加点却容易地多。所以我们用并查集判断联通性,然后逐个加点就好了。

在每次加点的时候,首先那个点会先成为一个新的连通块,然后每合并一次并查集,连通块数量就减少一个。注意,一开始我们要初始化连通块数量。

以下是AC代码:

#include<cstdio>
#include<iostream>
using namespace std;
struct Edge
{
    int nst,to;
}edge[500010];
int head[500010],cnt,f[500010],c[500010],n,m,ans[500010],k,t[500010];
void add(int a,int b)
{
    edge[++cnt].nst=head[a];
    edge[cnt].to=b;
    head[a]=cnt;
}
int find(int a)
{
    return f[a]==a?a:f[a]=find(f[a]);
}
int main()
{
    scanf("%d%d",&n,&m);
    int a,b;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        add(a,b);add(b,a);
    }
    scanf("%d",&k);
    
    for(int i=0;i<k;i++)
    {
        scanf("%d",&c[i]);
        t[c[i]]=1;
    }
    ans[k]=n-k;
    
    for(int i=0;i<n;i++)//并查集初始化
    f[i]=i;
    
    for(int i=0;i<n;i++)//初始的连通块数量
    if(!t[i])//如果起点
    {
        for(int j=head[i];j;j=edge[j].nst)
        {
            int v=edge[j].to;
            if(!t[v])//和终点都没被摧毁
            {
                a=find(v);b=find(i);
                if(a!=b)f[a]=b,ans[k]--;//就合并一次,减少一个连通块数量
            }
        }
    }
    
    for(int i=k-1;i>=0;i--)//倒序逐个加点
    {
        ans[i]=ans[i+1]+1;t[c[i]]=0;
        for(int j=head[c[i]];j;j=edge[j].nst)
        {
            int v=edge[j].to;
            if(!t[v])//还没加入的点不能取
            {
                a=find(v);b=find(c[i]);
                if(a!=b)f[a]=b,ans[i]--;
            }
        }
    }
    
    for(int i=0;i<=k;i++)
    printf("%d\n",ans[i]);
    return 0;
 } 

第九题:P1337 平衡点

题解思路:模拟退火(需要看脸

由于电脑问题,目前写博客比较困难,先贴代码:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#define re register
using namespace std;
struct node { int x,y,w;};
node a[1010];
int n,sx,sy;
double ansx,ansy; //全局最优解的坐标
double ans=1e18,t;//全局最优解、温度
const double delta=0.993;//降温系数
inline double calc_energy(double x,double y) //计算整个系统的能量
{ 
    double rt=0;
    for(re int i=1;i<=n;i++) 
    {
        double deltax=x-a[i].x,deltay=y-a[i].y;
        rt+=sqrt(deltax*deltax+deltay*deltay)*a[i].w;
    }
    return rt;
}
inline void simulate_anneal() //SA主过程
{
    double x=ansx,y=ansy;
    t=2000; //初始温度
    while (t>1e-14) 
    {
        double X=x+((rand()<<1)-RAND_MAX)*t;
        double Y=y+((rand()<<1)-RAND_MAX)*t;//得出一个新的坐标
        double now=calc_energy(X,Y);
        double Delta=now-ans;
        if (Delta<0) //接受
        {
            x=X,y=Y;
            ansx=x,ansy=y,ans=now;
        }
        else if (exp(-Delta/t)*RAND_MAX>rand()) x=X,y=Y;//以一个概率接受
        t*=delta;
    }
}

inline void Solve() //多跑几遍SA,减小误差
{ 
    ansx=(double)sx/n,ansy=(double)sy/n; //从平均值开始更容易接近最优解
    simulate_anneal();
    simulate_anneal();
    simulate_anneal();
}

int main() 
{
    srand(19260817);
    srand(rand());//玄学srand
    srand(rand());
    scanf("%d",&n);
    for(re int i=1;i<=n;i++)
    {
        scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
        sx+=a[i].x,sy+=a[i].y;
    }
    Solve();
    printf("%.3f %.3f\n",ansx,ansy);
    return 0;
}

第十题:P1325 雷达安装

题解思路:排序+贪心

由于雷达站只能建在海岸上,所以很容易想到这是一个区间覆盖的题(@P1514 引水入城)。所以说,我们先把每个岛屿对应的区间处理出来,再按照右端点进行升序排序(如果右端点一样就按照左端点降序排序),如果下一个区间的左端点的坐标比当前区间的右端点小,就ans++,最后输出答案

以下是AC代码:

#include<cmath>
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
struct Node
{
    double l,r;
}a[1010];
int n,d,x,y,cnt,ans;
double cur=-0x3f3f3f3f;
double calc(int a)
{
    return sqrt(d*d-a*a);
}
bool cmp (const Node &x,const Node &y)
{
    if(x.r!=y.r)return x.r<y.r;
    return x.l>y.l;
}
int main()
{
    scanf("%d%d",&n,&d);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&x,&y);
        double c=calc(y);
        a[++cnt].l=x-c;
        a[cnt].r=x+c;
    }
    sort(a+1,a+1+n,cmp);
    for(int i=1;i<=n;i++)
    {
        if(cur<a[i].l)
        {
            cur=a[i].r;
            ans++;
        }
        else continue;
    }
    printf("%d",ans);
    return 0;
} 

转载于:https://www.cnblogs.com/xsx-blog/p/11344624.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值