大连海事大学第十届程序设计竞赛 题解

题目链接:传送门

题目难度排序:
I 、J(题面比较坑) 、A(会算相交就很简单了) < C 、G 、D< K、E < B、H < F
(个人认为的难度排序)

前情提要:read可以近似等价于scanf,前面一堆宏定义可以忽略不看。

A.宣传比赛
看似计算几何,实则简单暴力,对于每一个海报,向后遍历算出相交面积并求和。
坑点:注意只有一张海报的情况

AC

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cctype>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pLL;
const int N=5e3;
const double inf=0x3f3f3f3f;
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define debug puts("...")
LL read()
{
    LL x=0,t=1;
    char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-')t=-1; ch=getchar(); }
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
LL x1[N],yy1[N],x2[N],y2[N];
int main()
{
    int n=read();
    for(int i=1;i<=n;i++)
    {
        x1[i]=read();
        yy1[i]=read();
        x2[i]=read();
        y2[i]=read();
    }
    LL ans=1,maxn=0;
    for(int i=1;i<=n;i++)
    {
        LL sum=0;
        for(int j=i+1;j<=n;j++)
        {
            LL l=min(x2[i],x2[j])-max(x1[i],x1[j]);
            LL r=min(y2[i],y2[j])-max(yy1[i],yy1[j]);
            if(l<0||r<0) continue;
            sum+=l*r;
        }
        if(sum>maxn)
        {
            maxn=sum;
            ans=i;
        }
    }
    printf("%lld %lld\n",ans,maxn);
    return 0;
}

B.DPJ同学来到了魔法王国
【由codeforces题目改编】
解析及代码见 :传送门

C.魔法游戏
如果由一个数字的xi<n/m ,那么我们肯定要操作使其他数字增加,把他们补充到 xi,使得xi的数量变大,那么代码就出来了,直接暴力的做法是O(n^2),需要遍历xj(j!=i) 来补充自身,但是我们是要使答案最小,所以能找相近的xj就找相近的。但显然会超时,所以在此给定两种做法:

第一种:
O(nlogm)
把思路转化,之前思路是遍历xj让不满足n/m的xi得到补充,而反过来就是把xj多余的数量加到不够n/m的xi上。

将输入的数字a[k] 增加(如果 xk = a[k] % m 的数量已到达 n/m),即向上查找一个最小的xi,并且xi的数量是不够的。

那么维护一个集合set。先把所有xi(0~m-1)插入 一个集合set。如果有满足题意的xi (xi==n/m),就把它从集合删掉,否则就在集合中查找比他大的第一个数(二分),再把差值加到答案上。

AC1

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pLL;
const int N=2e5+5;
const double inf=0x3f3f3f3f;
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define debug puts("...")
LL read()
{
    LL x=0,t=1;
    char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-')t=-1; ch=getchar(); }
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
int cnt[N];
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        cnt[x%m]++;
    }
    LL ans=0;
    int k=n/m;
    for(int i=0;i<m;i++)
    {
        if(cnt[i]<=k) continue;
        ans+=cnt[i]-k;
        cnt[i+1]+=cnt[i]-k;
        cnt[i]=k;

    }
    cnt[0]+=cnt[m];
    for(int i=0;i<m;i++)
    {
        if(cnt[i]<=k) continue;
        ans+=cnt[i]-k;
        cnt[i+1]+=cnt[i]-k;
    }
    printf("%lld\n",ans);
    return 0;
}

做法二:
O(n+m)
因为要找最近的,那可以直接一边循环把多余的数字往后一个数字加,就相当于是整体的多余的数字,一起找一个最近的数字了。
一遍循环后,前面的没有达到n/m的xi仍然不够,但靠后的已经得到补充了,并且所有多余的都会汇集到 x[m] , 而m%m=0,所以把x[m]的个数加到x[0]上,再做一遍遍历即可。

AC2

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pLL;
const int N=2e5+5;
const double inf=0x3f3f3f3f;
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define debug puts("...")
LL read()
{
    LL x=0,t=1;
    char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-')t=-1; ch=getchar(); }
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
int cnt[N];
int main()
{
    int n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        int x=read();
        cnt[x%m]++;
    }
    LL ans=0;
    int k=n/m;
    for(int i=0;i<m;i++)
    {
        if(cnt[i]<=k) continue;
        ans+=cnt[i]-k;
        cnt[i+1]+=cnt[i]-k;
        cnt[i]=k;

    }
    cnt[0]+=cnt[m];
    for(int i=0;i<m;i++)
    {
        if(cnt[i]<=k) continue;
        ans+=cnt[i]-k;
        cnt[i+1]+=cnt[i]-k;
    }
    printf("%lld\n",ans);
    return 0;
}

D. 今天你吃药了吗?
卡特兰数模板题(会就是会,不会就是不会……),
也可以用计数DP(动态规划简称)。

卡特兰数AC代码

#include <bits/stdc++.h>
using namespace std;
int h[110][105];
void init()
{
    h[0][0]=1;
    h[0][1]=1;
    h[1][0]=1;
    h[1][1]=1;
    for(int i=2;i<=100;i++)
    {
        for(int j=1;j<=h[i-1][0];j++)
            h[i][j]=h[i-1][j]*(4*i-2);
        for(int j=1;j<=100;j++)
        {
            h[i][j+1]+=h[i][j]/10;
            h[i][j]=h[i][j]%10;
        }
        h[i][0]=100;
        while(h[i][h[i][0]]==0)
            h[i][0]--;
        int t=0;
        for(int j=h[i][0];j>=1;j--)
        {
            t=t*10+h[i][j];
            h[i][j]=t/(i+1);
            t%=(i+1);
        }
        while(h[i][h[i][0]]==0)
            h[i][0]--;
    }
}
int main()
{
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    init();
    int n;
    while(scanf("%d",&n),n)
    {
        for(int i=h[n][0];i>=1;i--)
            printf("%d",h[n][i]);
        puts("");
    }
    return 0;
}

DP:
状态转移:dp[i][j]=dp[i-1][j]+dp[i][j-1];(i表示有i片完整,j表示有j片不完整)
最终答案为:dp[n][n];
AC代码

#include <bits/stdc++.h>
using namespace std;
struct node
{
    int num[105];
    node operator + (const node b)const
    {
        node res;
        memset(res.num,0,sizeof(res.num));
        for(int i=1;i<=max(num[0],b.num[0]);i++)
            res.num[i]=num[i]+b.num[i];
        res.num[0]=max(num[0],b.num[0]);
        for(int i=1;i<=res.num[0];i++)
        {
            res.num[i+1]+=(res.num[i]/10);
            res.num[i]=res.num[i]%10;
        }
        res.num[0]++;
        while(res.num[res.num[0]]==0)
            res.num[0]--;
        return res;
    }
}dp[110][110];
void init()
{
    for(int i=0;i<=105;i++)
    {
        dp[i][0].num[1]=1;
        dp[i][0].num[0]=1;
    }
    for(int i=1;i<=100;i++)
        for(int j=1;j<=i;j++)
            dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
int main()
{
    int n;
    init();
    while(scanf("%d",&n),n)
    {
        for(int i=dp[n][n].num[0];i>=1;i--)
            printf("%d",dp[n][n].num[i]);
        printf("\n");
    }
    return 0;
}

E. lcy和最小生成树
思路:求补图的连通块个数,bfs,不过由于是完全图,边数过多,所以每找到一个点要删去一个点。这里面涉及一些贪心思想,首先要是生成树权值最小,肯定是尽量使生成树中更多的边的边权为0,因此将0视为连边,1视为断开,求连通块个数再-1即时答案。

#include<bits/stdc++.h>
using namespace std;
#define maxn 200005
#define INF 0x3f3f3f3f
#define inc(i,x,y) for(int i=x;i<=y;i++)
#define ll long long int
int n,m;
set<int>g[maxn],vis;
bool vv[maxn];
void dfs(int x){
    vv[x]=1;
    vis.erase(x);
    set<int>::iterator it;
    vector<int>v;
    for(it=vis.begin();it!=vis.end();it++){
        int xx=*it;
        if(g[x].count(xx)==0){
            v.push_back(xx);
        }
    }
    for(int i=0;i<v.size();i++){
        vis.erase(v[i]);
    }
    for(int i=0;i<v.size();i++){
        dfs(v[i]);
    }
}
int main()
{
    //freopen("input8.txt","r",stdin);
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        for(int i=0;i<=n;i++){vv[i]=0;g[i].clear();}
        vis.clear();
        int x,y;
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x,&y);
        g[x].insert(y);
        g[y].insert(x);
    }
    inc(i,1,n)vis.insert(i);
    int ans=0;
    inc(i,1,n){
        if(vv[i]==0){
            ans++;
            dfs(i);
        }
    }
    printf("%d\n",ans-1);
    }

    return 0;
}

F. Lcy疯狂补作业
在这里插入图片描述
在这里插入图片描述
AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+5;
int n,m;
ll p,ans;
ll c[N],inv[N];
int prime[N];
bool vis[N];
int mu[N],phi[N];
void mobius_euler()
{
    memset(vis,0,sizeof(vis));
    mu[1]=1;
    phi[1]=1;
    int cnt=0;
    for(int i=2;i<N;i++)
    {
        if(!vis[i])
        {
            prime[++cnt]=i;
            mu[i]=-1;
            phi[i]=i-1;
        }
        for(int j=1;j<=cnt&&i*prime[j]<N;j++)
        {
            int tmp=i*prime[j];
            vis[tmp]=1;
            if(i%prime[j]==0)
            {
                mu[tmp]=0;
                phi[tmp]=phi[i]*prime[j];
                break;
            }
            else
            {
                mu[tmp]=-mu[i];
                phi[tmp]=phi[i]*phi[prime[j]];
            }
        }
    }
}
void init()
{
    inv[1]=1;
    for(int i=2;i<=min(n,m);i++)//递推求逆元
        inv[i]=inv[p%i]*(ll)(p-p/i)%p;
    for(int i=1;i<=min(n,m);i++)
        c[i]=(ll)i*inv[phi[i]]%p;
}
ll g(int x,int y)
{
    ll res=0;
    for(int i=1;i<=min(x,y);i++)
        res=(res+(ll)mu[i]*(x/i)*(y/i))%p;
    return res;
}
int main()
{
    int t;
    //freopen("input25.txt","r",stdin);
    //freopen("output25.txt","w",stdout);
    scanf("%d",&t);
    mobius_euler();
    while(t--)
    {
        scanf("%d%d%lld",&m,&n,&p);
        ans=0;
        init();
        for(int i=1;i<=min(n,m);i++)
            ans=(ans+c[i]*g(m/i,n/i))%p;
        printf("%lld\n",ans);
    }
    return 0;
}

G. LYY双11剁手后的悲惨生活
显然,偶数能够向前移动的最大位置 是 前一个偶数的后一个位置,奇数同理,那么可以维护两个队列,先将奇偶数分别放入两个队列里,最后答案就是不断输出两个队列的队首最小值。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=3e5+5;
const int inf=0x3f3f3f3f;
queue<int> q1,q2;
char s[N];
int ans[N],tot;
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        tot=0;
        scanf("%s",s+1);
        int n=strlen(s+1);
        for(int i=1;i<=n;i++)
        {
            char x=s[i];
            if(x&1) q1.push(x-'0'); //x&1 判断是否是奇数
            else q2.push(x-'0');
        }
        while(!q1.empty()&&!q2.empty())
        {
            if(q1.front()>q2.front())
            {
                ans[++tot]=q2.front();
                q2.pop();
            }
            else
            {
                ans[++tot]=q1.front();
                q1.pop();
            }
        }
        while(!q1.empty())
        {
            ans[++tot]=q1.front();
            q1.pop();
        }
        while(!q2.empty())
        {
            ans[++tot]=q2.front();
            q2.pop();
        }
        for(int i=1;i<=tot;i++)
            printf("%d",ans[i]);
        putchar('\n');
    }
	return 0;
}

AC代码

H. 大佬的考验
【改编自codeforces1234F】
可参考另一篇博客:传送门

I. 就决定是你了
对Treecko 的每一个字符计数,求出所有字符中数量最小的(‘e’的数量要除以2);

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+5;
const int inf=0x3f3f3f3f;
char s[N];
int cnt[10];
int main()
{
	scanf("%s",s);
	int n=strlen(s);
	for(int i=0;i<n;i++)
    {
        if(s[i]=='T') cnt[0]++;
        else if(s[i]=='r') cnt[1]++;
        else if(s[i]=='e') cnt[2]++;
        else if(s[i]=='c') cnt[3]++;
        else if(s[i]=='k') cnt[4]++;
        else if(s[i]=='o') cnt[5]++;
    }
    int ans=inf;
    cnt[2]>>=1; //cnt[2]/=2;
    for(int i=0;i<=5;i++) ans=min(ans,cnt[i]);
    printf("%d\n",ans);
	return 0;
}

J. wxy爱数电

签到题,把二进制数转化成十进制数相加即可,但注意这是48位,会爆int,要开long long 存。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=55;
int main()
{
	char s1[N],s2[N];
	while(scanf("%s %s",s1,s2)!=EOF)
    {
        LL t1=0, t2=0;
    	int l1=strlen(s1),l2=strlen(s2);
		for(int i=0;i<l1;i++)
			t1<<=1,t1+=s1[i]-'0'; //等价于 t = 2 * t + s[i] - '0';
   		for(int i=0;i<l2;i++)
			t2<<=1,t2+=s2[i]-'0';
	
		printf("%lld\n",t1+t2);
    }
    return 0;
	
}

K. 立方和问题

【2019亚洲区域赛(徐州站 ) 铜牌题改编】
a^3 + b^3 + c^3 = x , |a|,|b|,|c|<=5000 , 0<=x<=200
这道题可以考虑枚举,-5000<=a,b,c<=5000

最朴素的算法,枚举a,b,c ,判断立方和是否等于200以内的自然数, 复杂度O(n^3),显然超时。

优化算法:a^3 + b^3 = x - c^3。枚举 a,b ,并把两数立方和存入 映射map或集合set(红黑树/AVL(BST)/hash表) 中,之后每输入x,就枚举c, 判断 map[ x+ c^3 ]是否等于1, 复杂度O(n^2*logn) ,外加一堆常数,并且map在数字多的情况下,运行效率大大退化,在一个小时内基本跑不出来。

cqrt:立方根 ; ceil: 向上取整 ; floor: 向下取整

打表: 同样的,我们枚举 a,b ,之后再枚举c=cqrt(x - a^3 - b^3 ),x∈[0,200] ,c需要枚举的范围实际上只有 [ floor( cqrt(- a^3 - b^3) ) , ceil( cqrt(200 - a^3 - b^3) ) ] ,而当a^3 或 b^3很大时,这个范围长度是一定 小于 cqrt(200) 的 ,因为200 对于 a^3 + b^3的值基本没有什么影响。

当 a-> ∞ b->∞ , 这个范围的长度 -> 无穷小。再看三个数的立方和是否在 [ 0 , 200 ] ,若是 就把答案存起来,最后一起打印出来。

所以 复杂度 O(n^2) ,并且常数是在可接受范围内的,大概十多秒就能跑出答案,
我们把打出来的答案用一个小的数组存起来,之后所有询问直接 O(1) 输出数组里存的答案即可。

当然在本地上打表,可以开O2 和 用 register

打表代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
typedef pair<LL,LL> pLL;
const int N=5e3;
const double inf=0x3f3f3f3f;
#define ls (i<<1)
#define rs (i<<1|1)
#define fi first
#define se second
#define mk make_pair
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define debug puts("...")
LL read()
{
    LL x=0,t=1;
    char ch=getchar();
    while(!isdigit(ch)){ if(ch=='-')t=-1; ch=getchar(); }
    while(isdigit(ch)){ x=10*x+ch-'0'; ch=getchar(); }
    return x*t;
}
struct node
{
    LL x,y,z;
    node(){ x=y=z=inf; }
    node(LL xx,LL yy,LL zz)
    {
        x=xx; y=yy; z=zz;
    }
}ans[205];
LL cnt=0;
int main()
{
    for(LL a=-N;a<=N;a++)
    {
        for(LL b=a;b<=N;b++)
        {

            LL l=-a*a*a-b*b*b,r=200-a*a*a-b*b*b;
            LL tl=l>=0?1:-1,tr=r>=0?1:-1;
            LL ll=tl*pow(abs(l),1.0/3),rr=tr*pow(abs(r),1.0/3);
           
            for(LL c=ll;c<=rr;c++)
            {
                LL tmp=a*a*a+b*b*b+c*c*c;
                if(tmp<=200&&tmp>=0)
                    ans[tmp]=node(a,b,c);//原题是输出解,所以这里把解存下来
            }
           
        }
    }
    for(int i=0;i<=200;i++)
        if(ans[i].x!=inf) printf("ans[%d]=YES\n",i);//打印出答案,复制到一个新的程序, 提交并AC
        else printf("ans[%d]=NO\n",i);
    return 0;
}
posted @ 2019-12-11 21:14  DeepJay  阅读(236)  评论(0编辑  收藏  举报