W
e
l
c
o
m
e
: )

ZYCode Normal Round #1 解题报告

ZYCode Normal Round #1

首先感谢piggy123和zxx两位巨佬对本次比赛的命题,提供了有难度、有深度的题目和极其良好的比赛体验。(指A题数据挂掉和std fAKe

A. 金币收集加强版(虚假的E题

形式化题意

给定\(n\)个数\(1-n\),从中选择一些数,满足若选择\(x\),则无法选择\(2x-1,2x,2x+1\),反之,若选择\(2x-1,2x\)\(2x+1\),则无法选择\(x\)

问如何选择使选择的数的和最大,答案对\(114514\)取模,有多组测试数据。

思路

首先容易发现想要令和最大就必须优先选较大的数。读者自证不难

因为前\(60\%\)的数据\(n\)仅到\(10^7\),考虑用\(bool\)数组存储每个数是否被删去。用于对拍(get \(60\) \(pts\)

如果观察\(bool\)数组容易发现有许多段区间的数被忽略,设当前考虑范围的最大数为\(x\),易知离\(x\)“最近”的保留的区间的下界是\(\displaystyle\left\lceil \frac{x}{2}\right\rceil+1\),上界是\(x\),这些数的和容易想到用等差数列求和公式算。(虽然在模\(114514\)意义下\(2\)不存在逆元,但是只要你在除以\(2\)后再取模就绝对没有问题)

进一步观察发现在最近的忽略区间之后并不是全部都保留,容易发现最近忽略区间的下界的上一个数为\(\displaystyle\left\lfloor\frac{\left\lceil \displaystyle\frac{x}{2}\right\rceil+1}{2}\right\rfloor-1\)而又可以令\(x\)为上述值计算剩余区间的答案,只需递归地重复上述过程即可。

另外这题开ull也会溢出,建议使用__int128,懒得写快读快写可以用类型转换,因为保证输入和输出不会爆long long。

AC代码

#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)

using namespace std;
__int128 n;
const int mod=114514;
__int128 f(__int128 x)
{
    if(x==0)
    {
        return 0ll;
    }
    if(x==1)
    {
        return 1ll;
    }
    __int128 ret=0;
    __int128 bound=((x+1)>>1)+1;
    ret=(ret+(((x+bound)*(x-bound+1))>>1)%mod)%mod;
    ret=(ret+f((bound>>1)-1))%mod;
    return ret;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        long long tmp;
        cin>>tmp;
        n=tmp;
        cout<<(long long)f(n)<<endl;
    }
    return 0;
}

B. 多K音游

首先这道题有一种fAKe的做法
2. 用两个计数器记录手指空闲的时间,遇到键就移动小于等于计数器的距离点击并清空计数器(见第一组测试数据)

真·做法

考虑爆搜,但由于爆搜在遇到大量空行时,状态数会呈指数级增长,

考虑DP,\(dp[i][l][r]\)表示第\(i\)行,左手在\(l\),右手在\(r\)是否能实现

易知若\(dp[i][l][r]=1\),则\(dp[i+1][l-1][r],dp[i+1][l][r],dp[i+1][l+1][r],\cdots=1\)

但是本题空间限制设为\(16\) \(MiB\),因此使用滚动数组优化,并且只转移符合\(key\)位置的状态

另外由于音游的谱面呈瀑布式下落,需要反向存储

AC代码

#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)
using namespace std;
int n,m,s1,s2;
bool dp[255][255],ndp[255][255];
int cnt[255],pos[255][3];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m>>s1>>s2;
    for(int i=n;i>0;--i)
    {
        for(int j=1;j<=m;++j)
        {
            int tmp;
            cin>>tmp;
            for(int k=1;k<=tmp;++k)
            {
                ++cnt[i];
                pos[i][cnt[i]]=j;
            }
        }
    }
    dp[s1][s2]=1;
    for(int i=1;i<=n;++i)
    {
        for(int j=1;j<=m;++j)
        {
            for(int k=j;k<=m;++k)
            {
                for(int d1=-1;d1<=1;++d1)
                {
                    for(int d2=-1;d2<=1;++d2)
                    {
                        if(dp[j+d1][k+d2])
                        {
                            ndp[j][k]=1;
                        }
                    }
                }
            }
        }
        memset(dp,0,sizeof(dp));
        if(cnt[i]==2)
        {
            if(!ndp[pos[i][1]][pos[i][2]])
            {
                cout<<i<<endl;
                return 0;
            }
            dp[pos[i][1]][pos[i][2]]=1;
        }
        else if(cnt[i]==1)
        {
            bool flag=0;
            for(int j=1;j<=pos[i][1];++j)
            {
                if(ndp[j][pos[i][1]])
                {
                    flag=1;
                    break;
                }
            }
            if(!flag)
            {
                for(int j=pos[i][1]+1;j<=m;++j)
                {
                    if(ndp[pos[i][1]][j])
                    {
                        flag=1;
                        break;
                    }
                }
            }
            if(!flag)
            {
                cout<<i<<endl;
                return 0;
            }
            for(int j=1;j<=pos[i][1];++j)
            {
                dp[j][pos[i][1]]=ndp[j][pos[i][1]];
            }
            for(int j=pos[i][1]+1;j<=m;++j)
            {
                dp[pos[i][1]][j]=ndp[pos[i][1]][j];
            }
        }
        else
        {
            for(int j=1;j<=m;++j)
            {
                for(int k=j;k<=m;++k)
                {
                    dp[j][k]=ndp[j][k];
                }
            }
        }
        memset(ndp,0,sizeof(ndp));
    }
    cout<<"Full Combo!"<<endl;
    return 0;
}

C. 联盟加强版

首先注意到\(m<n\),因此此题的敌对关系一定是一个森林

简单情况

考虑若\(w\)均为\(-1\),则问题变为若选择一个节点后,它的父节点和子节点均无法被选择,每个节点有一个权值,求权值最大和。这个问题十分典型,即为“周年纪念晚会”(详见)。但由于此题的关系为一个森林,因此需要从多个根计算。

复杂情况

既然只需花费\(w\)的代价即可包含敌对节点,那只需再考虑已选择一个节点后,对最选择此节点的贡献\(-w\)和不选择此节点的贡献取\(max\)即可

AC代码

#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)

using namespace std;
long long c[1000005],dp[1000005][2];
bool ign[1000005];
vector <pair <long long,long long>> enemy[1000005];
void calc(long long u,long long parent)
{
    ign[u]=1;
    dp[u][0]=0;
    dp[u][1]=c[u];

    for (pair <long long,long long> vw:enemy[u])
    {
        long long v=vw.first,w=vw.second;

        if(v==parent)
        {
            continue;
        }

        calc(v,u);
        dp[u][0]+=max(dp[v][0],dp[v][1]);

        if(w==-1)
        {
            dp[u][1]+=dp[v][0];
        }
        else
        {
            dp[u][1]+=max(dp[v][0],dp[v][1]-w);
        }
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    long long n,m;
    cin>>n>>m;

    for (long long i=1; i<=n; i++)
    {
        cin>>c[i];
    }

    while(m--)
    {
        long long u,v,w;
        cin>>u>>v>>w;
        enemy[u].push_back({v,w});
        enemy[v].push_back({u,w});
    }

    long long ans=0;

    for(long long i=1; i<=n; ++i)
    {
        if(!ign[i])
        {
            calc(i,-1);
            ans+=max(dp[i][0],dp[i][1]);
        }
    }

    cout<<ans<<endl;
    return 0;
}

D. 图的终结

显然结论

若先手无法一次直接取胜,则一定平局

思路

首先计算出去重后的边数(去重指删除任何多余的边,例如三角形中的任意一条边),显然可以用并查集实现

需要增加的边的边数\(=\)点数\(-1-\)去重后边数

需要删除的边的边数\(=\)原边数\(-\)去重后的边数

AC代码

#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)

using namespace std;
int R[1000005];
int findr(int x)
{
    if(R[x]==x)
    {
        return x;
    }
    R[x]=findr(R[x]);
    return R[x];
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n,m,k,s=0;
    cin>>n>>m>>k;
    for(int i=1;i<=n;++i)
    {
        R[i]=i;
    }
    for(int i=0;i<m;++i)
    {
        int u,v;
        cin>>u>>v;
        int r1=findr(u),r2=findr(v);
        if(r1!=r2)
        {
            R[r2]=r1;
            ++s;
        }
    }
    int ans=(n-1-s)/*增边变树*/+(m-s)/*删边变树*/;
    cout<<(ans<=k?'Z':'Q')<<endl;
    return 0;
}

E. 经济建设(真正的E题)

关于Dijkstra和SPFA

虽然堆优化的\(Dijkstra\)算法的时间复杂度众说纷纭,但在完全图中,边数为\(n^2\)级别,无论是否使用堆优化或者哪种时间复杂度都是会TLE的,因此\(Dijkstra\)算法不可取。

至于SPFA,它已经死了。

正解

既然\(Dijkstra\)不可取,则只能考虑\(Floyed\)

我们不妨回忆一下\(Floyed\)的三重循环的意义

  1. 枚举中转点
  2. 枚举起点
  3. 枚举终点

而这道题背后的几何背景注定绝大部分关于中转点的枚举是无意义的,毕竟一般情况下两点之间线段最短。

而这是否意味着所有点之间的最短路径一定是连接两点的线段呢?

显然不是!

因为本题有一些称为“主城”的特殊点,由于经过主城可能减去\(x\%\)的路径,因此只要经过了主城那么路径就有可能被修改,或者说尝试不经过主城的松弛操作是无意义的,因此\(Floyed\)的第一重循环只需循环到\(k\),时间复杂度自然降为\(O(kn^2)\).

AC代码

#include <bits/stdc++.h>
#define ifile(x) freopen(x,"r",stdin)
#define ofile(x) freopen(x,"w",stdout)
#define iofile(x,y) freopen(x,"r",stdin),freopen(y,"w",stdout)

using namespace std;
pair <int,int> pos[2505];
double f[2505][2505];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n,m,x;
    cin>>n>>m>>x;
    for(int i=1;i<=n;++i)
    {
        cin>>pos[i].first>>pos[i].second;
    }
    for(int i=1;i<=n;++i)
    {
        for(int j=i+1;j<=n;++j)
        {
            f[i][j]=sqrt((pos[i].first-pos[j].first)*(pos[i].first-pos[j].first)+(pos[i].second-pos[j].second)*(pos[i].second-pos[j].second));
            if(i<=m&&j<=m)
            {
                f[i][j]*=(1.0-x*1.0/100.0);
                f[j][i]*=(1.0-x*1.0/100.0);
            }
        }
    }
    for(int k=1;k<=m;k++)
    {
        for(int x=1;x<=n;x++)
        {
            for(int y=x+1;y<=n;y++)
            {
                f[x][y]=min(f[x][y],f[x][k]+f[k][y]);
            }
        }
    }
    cout<<fixed<<setprecision(1);
    for(int i=1;i<=n;++i)
    {
        for(int j=i+1;j<=n;++j)
        {
            cout<<(i<=j?f[i][j]:f[j][i])<<' ';
        }
        cout<<endl;
    }
    return 0;
}
posted @ 2022-09-17 16:29  OberNotFound  阅读(79)  评论(0)    收藏  举报