大连海事大学第十届程序设计竞赛 题解
题目链接:传送门
题目难度排序:
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;
}