(搬一下ak小锋的题解)
ProblemA 神秘的三位数(代码填空)
问题描述 有这样⼀一个3位数,组成它的3个数字阶乘之和正好等于它本身。即:abc =a! +b! +c! 下⾯面的程序⽤用于搜索这样的3位数。请补全缺失的代码。
解题思路 模拟,将所有三位数的数字阶乘算出来和原数字⽐比较即可找出,题中阶乘数组已给,因此空 格部分代码为sum+=jc[x%10];
Problem B 用一个正整数产⽣生一个回文数
问题描述 若⼀一个数(⾸首位不不为0)从左到右读与从右到左读都是⼀一样,这个数就叫做回⽂文数,例例如12521 就是⼀一个回⽂文数。 给定⼀一个正整数,把它的每⼀一个位上的数字倒过来排列列组成⼀一个新数,然后与原数相加,如 果是回⽂文数则停⽌止,如果不不是,则重复这个操作,直到和为回⽂文数为⽌止。给定的数本身不不为 回⽂文数。 例例如,给定⼀一个正整数87则有: STEP1: 87+78=165 STEP2: 165+561=726 STEP3: 726+627=1353 STEP4: 1353+3531=4884 编写⼀一个程序,输⼊入M(12<=M<=1000) ,输出最少经过⼏几步可以得到回⽂文数。如果在8步以 内(含8步)不不可能得到回⽂文数,则输出0。
解题思路 暴力模拟,记录步数
AC 代码
include<bits/stdc++.h>
using namespace std;
int f(int a)
{
int b=0;
while(a)
{
b=b*10+a%10;
a/=10;
}
return b;
}
int main()
{
intT;
scanf("%d",&T);
while(T--)
{
int a;
scanf("%d",&a);
int ans=0;
while(ans<=8)
{
ans++;
a+=f(a);
if(a==f(a)) break;
}
if(ans>8) ans=0;
printf("%d\n",ans);
}
return 0;
}
Problem C 自守数
问题描述 自守数是指⼀一个数的平⽅方的尾数等于该数⾃自身的⾃自然数。例例如: 25^2=625 76^2=5776 9376^2=87909376 (其中的^表示次方,如:25^2 表示25的平方) 。 给你一批2147483647以内 的正整数,判断它们是不不是自守数,是输出Y,不不是输出N。例如: 输入:67276625,则必须输出:YNNY
解题思路 模拟,爆int 就用long long 嘛,a平方算出来,位数记录一下,对a平方取个模和原数比较即可。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int main()
{
int a;
while(scanf("%d",&a)!=EOF)
{
if(!a)
{
printf("Y");
continue;
}
long long b=a;
b=b*b;
long long c=1,d=a;
while(a)
{
c*=10;
a/=10;
}
if(b%c==d) printf("Y");
else printf("N");
}
return 0;
}
Problem D 高兴天数
问题描述 小X性格很独特,如果她今天高兴度比上次⼀一样或更高,她就会很善良,相反,如果她今天高兴度比上次低,她就会很凶!现在已经知道小X在N天⾥里里每天的高兴度M。根据这N天 中她每天高兴度M,合理安排与她相处时间,使大家与小X友好相处尽量多天数。现在要 求计算出最多能和小X友好相处多少天。
解题思路 LIS,⽤用一个数组记录每个长度的最小尾数,遍历数据数组,二分出这个数作为尾数的最长子序列长度,并更新尾数数组与答案,时间复杂度O(nlogn)
AC 代码
#include<bits/stdc++.h>
using namespace std;
int data[30005]={0},d[30005];
int main()
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&data[i]);
for(int i=1;i<=n;i++)
d[i]=35000;
int ans=0;
for(int i=1;i<=n;i++)
{
int p=upper_bound(d+1,d+n+1,data[i])-d;
d[p]=data[i];
ans=max(ans,p);
}
printf("%d\n",ans);
return 0;
}
Problem E 作弊的发牌者
问题描述 lyt 正在与他的N-1(2<=N<=100)个朋友打牌。他们玩的牌一副为K(N<=K<= 100,000,K 为N的倍数)张。所有牌中,一共有M(M =K/N)张“好牌”,其余的K-M张为“差牌”。lyt是游戏的发牌者,很自然地,他想把所有好牌都留给自己。他热衷于获胜,即使为此必须采取一 些不正当的手段。 在若干局游戏后,lyt的朋友们开始怀疑lyt在游戏中作弊,于是他们想了个对策:使用新的发牌规则。规则具体如下: 1. lyt把牌堆的最上面一张发给他右边的单身狗; 2. 每当lyt 发完一张牌,他都得将牌堆顶部接下来的P(1<=P<=10)张牌放到底部去(一般把这个操作称为切牌) ; 3. 然后,lyt 对逆时针方向的下一头单身狗重复上述的操作; lyt 绝望地认为,他再也不不可能获胜了,于是他找到了你,希望你告诉他,将好牌放在初始牌堆的哪些位置,能够确保它们在发完牌后全集中到他手里。顺带说明一下,我们把牌堆顶的牌定义为1号牌,从上往下第二张定义为2号牌,依此类推。
解题思路 数据结构模拟,然后把自己被发到的牌丢进vector 排个序输出就好了了,时间复杂度O(kp)。 我刚开始想的双向链表,赛后才想到队列,比双向链表好写多了,太蠢了。那就给双向链表 代码随便看看好了。
AC 代码
#include<bits/stdc++.h>
using namespace std;
vector<int>V;
struct list
{
int a,nex,pre;
}data[100005];
int main()
{
int n,k,p;
scanf("%d%d%d",&n,&k,&p);
for(int i=1;i<=k;i++)
{
data[i].pre=i-1;
data[i].a=i;
data[i].nex=i+1;
}
data[k].nex=1;
data[1].pre=k;
int t=k/n;
int no=1;
for(int i=0;i<t;i++)
{
for(int j=0;j<n-1;j++)
{
int pre=data[no].pre;
int nex=data[no].nex;
data[pre].nex=nex;
data[nex].pre=pre;
no=data[no].nex;
for(int l=0;l<p;l++) no=data[no].nex;
}
V .push_back(no);
int pre=data[no].pre;
int nex=data[no].nex;
data[pre].nex=nex;
data[nex].pre=pre;
no=data[no].nex;
for(int l=0;l<p;l++) no=data[no].nex;
}
sort(V .begin(),V .end());
for(int i=0;i<V .size();i++) printf("%d\n",V[i]);
return 0;
}
Problem F 朋友
问题描述 有一个城镇,住着n个市⺠民。已知一些⼈人互相为朋友。引用一个名人的话说,朋友的朋友也是朋友。意思是说如果A和B是朋友,C和B是朋友,则A和C是朋友.你的任务是数出最大朋友组的人数。
解题思路 并查集,维护所有节点对应的集合,并且在根节点记录集合大小,每次合并更新并查集状态并更更新答案即可。
AC 代码
include<bits/stdc++.h>
using namespace std;
int fa[30005],cnt[30005];
int findf(int a)
{
if(a==fa[a]) return a;
else return fa[a]=findf(fa[a]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
fa[i]=i;
cnt[i]=1;
}
int ans=0;
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
int aa=findf(a),bb=findf(b);
if(aa!=bb)
{
fa[bb]=aa;
cnt[aa]+=cnt[bb];
ans=max(ans,cnt[aa]);
}
}
printf("%d\n",ans);
return 0;
}
Problem G 连接格点
问题描述 有一个M行N列的点阵,相邻两点可以相连。一条纵向的连线花费一个单位,一条横向的连线花费两个单位。某些点之间已经有连线了,试问至少还需要花费多少个单位才能使所有的点全部连通。
解题思路 因为边最多1000×999×2条,点最多1000×1000个,可以直接用kruskal 做最小生成树。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int fa[2000005];
int findf(int a)
{
if(a==fa[a]) return a;
else return fa[a]=findf(fa[a]);
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=2000005;i++) fa[i]=i;
int x1,y1,x2,y2,ans=0;
while(scanf("%d%d%d%d",&x1,&y1,&x2,&y2)!=EOF)
{
int a=x1*1005+y1;
int b=x2*1005+y2;
int aa=findf(a),bb=findf(b);
if(aa!=bb) fa[aa]=bb;
}
for(int i=1;i<n;i++)
for(int j=1;j<=m;j++)
{
int a=i*1005+j;
int b=a+1005;
int aa=findf(a),bb=findf(b);
if(aa!=bb) fa[aa]=bb,ans++;
}
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++)
{
int a=i*1005+j;
int b=a+1;
int aa=findf(a),bb=findf(b);
if(aa!=bb) fa[aa]=bb,ans+=2;
}
printf("%d\n",ans);
return 0;
}
Problem H 木棍
问题描述 在一个原始部落,有一些人要去打猎了了,每个人都要挑选自己的工具——两根木棍。一 个用作远距离投掷攻击,一个用作近距离搏斗。但是每个人都想挑到最好的,但这是不可能的。但是为了让多数⼈人满意,也为了减少大家的矛盾。部落领袖设计了一个矛盾指数,这个 指数就是每个人的不舒服指数和,不舒服指数就(L1-L2)^2(^代表次⽅方) ,其中L1,L2 分别是一个人的两根木棍的长度。
部落领袖决定让矛盾指数最少,于是他来向你寻求帮助,希望你能告诉他矛盾指数至少有多少。
解题思路 先对木棍长度排序,可以证明最优情况一定是所有人选择了相邻长度的木棍。接下来使用动态规划算法求解,dp[i][j]表示第i个⼈人取到第j根木棍的最小答案,状态转移方程为dp[i][j] 等于dp[i-1][0 到j-2]的最小值加上data[j]-data[j-1]的平方,做到i=n 时更新答案。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int data[2005];
int dp[505][2005];
int main()
{
int m,n,ans=0x3f3f3f3f;
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++) scanf("%d",&data[i]);
sort(data+1,data+1+m);
memset(dp,0x3f3f3f3f,sizeof(dp));
dp[0][0]=0;
for(int i=1;i<=n;i++)
{
int mi=dp[i-1][i*2-2];
for(int j=i*2;j<=m;j++)
{
mi=min(mi,dp[i-1][j-2]);
dp[i][j]=mi+(data[j]-data[j-1])*(data[j]-data[j-1]);
if(i==n) ans=min(ans,dp[i][j]);
}
}
printf("%d\n",ans);
return 0;
}
Problem I 采药
问题描述 xgs 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最 有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间, 每一株也有它⾃自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。 ”如果你是xgs,你能完成这个任务吗?
解题思路 裸01背包,但是N,M太大做不了,我们观察到所有草药花费的时间和价值都在10以内,因此我们可以把所有草药中花费时间和价值一样的合并,因此问题变成了了N为100(0无意义过滤掉),M为100000的多重背包,二进制优化T掉了。我们发现,对于背包容量%当前物品重量相等的情况,我们可以一起更新,并使⽤用单调队列优化,即可将时间复杂度优化成 O(NM),相对于二进制优化省了一个log,就能过了。
AC 代码
#include<bits/stdc++.h>
#define mp make_pair
using namespace std;
int data[15][15]={0};
int dp[100005];
pair<int,int> que[100005];
int main()
{
int ll=0,rr=0;
int n,m;
scanf("%d%d",&n,&m);
while(n--)
{
int a,b;
scanf("%d%d",&a,&b);
data[a][b]++;
}
for(int i=0;i<=10;i++)
for(int j=0;j<=10;j++)
if(data[i][j])
{
for(int k=0;k<i;k++)
{
ll=rr=0;
que[rr++]=mp(dp[k],0);
for(int l=1;l*i+k<=m;l++)
{
int nu=l*i+k;
while(ll<rr&&que[ll].first+(l-que[ll].second)*j<dp[nu]) rr--;
que[rr++]=mp(dp[nu],l);
while(l-que[ll].second>data[i][j]) ll++;
dp[nu]=que[ll].first+(l-que[ll].second)*j;
}
}
}
printf("%d\n",dp[m]);
return 0;
}
Problem J 三个袋子
问题描述
题目背景:cwz 在公园里游玩时捡到了很多小球,而且每个球都不一样。cwz 找遍了全身只发现了了3个一模一样的袋子。他打算把这些小球都装进袋子里(袋子可以为空) 。他想知道他总共有多少种放法。
问题描述: 将N个不同的球放到3个相同的袋子里,求放球的方案总数M。 结果可能很大,我们仅要求输出Mmod K的结果。 现在,平平已经统计出了了N<=10 的所有情况。⻅见下表: N 1 2 3 4 5 6 7 8 9 10 M 1 2 5 14 41 122 365 1094 3281 9842
解题思路 容易得出m[i]=m[i-1]*3-1,在m[1]=1 的情况下,可推出通项公式m[n]=(3^(n-1)+1)/2%k。由 于 2与 k 不一定互质,因此不不能转成乘法逆元。我们容易得出 a/b%k=a%(b×k)/b,因此 m[n]=(3^(n-1)%2k+1)/2,n比较大的情况考虑快速幂,时间复杂度O(logn)。
AC 代码
#include<bits/stdc++.h>
using namespace std;
long long kpow(int a,int b,intc)
{
long long ans=1;
long long tmp=a;
while(b)
{
if(b&1) ans=ans*tmp%c;
tmp=tmp*tmp%c;
b>>=1;
}
return ans;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
printf("%lld\n",(kpow(3,n-1,k<<1)+1)>>1);
return 0;
}
Problem K 广告收入
问题描述 大家都知道,lwx 的OI 商店是靠广告来获得收入的。而广告都是那些热心的顾客们点击的。每点击一次,Google就会给lwx的账户中存一定的钱。当该⽉月的收入大于等于100美元时,Google 才会把账户里的钱寄给lwx,然后lwx 就可以拿着这笔钱去资助失学儿童了! Google会在OI 商店的网页上随机的发布N家广告商的广告,每家广告商的广告的价格 (即Google支付给lwx的钱数)是不同的。这个月有M个热心的顾客点击了广告。 假如说资助一个失学儿童需要K美元的话。现在已经知道了了这N家⼴广告商的价格,也 知道了了M个热心的顾客都点了了哪些广告。请你帮lwx算一下,他最多能资助几个失学儿童呢?
解题思路 用map 存下广告价格,然后直接计算即可。
AC 代码
#include<bits/stdc++.h>
using namespace std;
map<string,double>M;
int main()
{
int n,m,k;
cin>>n>>m>>k;
while(n--)
{
string a;
double b;
cin>>a>>b;
M[a]=b;
}
double ans=0.0;
while(m--)
{
string a,b;
cin>>a>>b;
ans+=M[b];
}
cout<<floor(ans/k)<<endl;
return 0;
}
Problem L 招聘
问题描述 Alice新开了一家公司,它的下面有两个项目,分别需要N1和N2个⼈人来完成。现在有N个人前来应聘,于是Alice通过⾯面试来决定他们中的哪些⼈人会被录用。
Alice在面试中,会仔细考察他们能如何为公司的项目带来收益。她给每个人打了两个分值Q1和Q2,表示他加入第一个和第二项目分别能带来的收益值。同时,她也会仔细考察他们每个人的缺点,并且给每人打了另两个分值C1和C2,表示他们进入每个项目可能带来的负面效应。Alice心目中的最优决策是,在决定好录用哪些人以及每个人在哪个项目下工作之后,他们为公司带来的收益总和,除以他们为项目带来的负面效应总和,这个比值要最大。 你能帮他计算出在最优决策下,这个比值为多少吗? 前来应聘的人数总是大于等于两个项目需求人数的总和,因此 Alice 一定会恰好招 N1+N2 个人,分配给第一个项目N1个人,分配给第二个项目N2个人,没有人会同时属于两个项目。
解题思路 对于答案r,存在一种分配⽅方案,使得Q的总和等于r乘上C的总和,根据这个性质二分答 案,使⽤用动态规划法求出对应 r 的最大 Q 的总和-r 乘上 C 的总和,dp[i][j][k]表示做到前 i 个人,j个人招去做项目1,k个人招去做项目2情况下上述的最大答案,dp[i][j][k]为以下三种情况的最大值 dp[i-1][j][k](不招这个人) dp[i-1][j-1][k]+data[i].q1-data[i].c1*r(招这个人做项目1) dp[i-1][j][k-1]+data[i].q2-data[i].c2*r(招这个人做项目2)
AC 代码
#include<bits/stdc++.h>
using namespace std;
struct p
{
double q1,q2,c1,c2;
void input()
{
scanf("%lf%lf%lf%lf",&q1,&c1,&q2,&c2);
}
}data[55];
double dp[55][55][55];
int n,n1,n2;
bool isok(double r)
{
for(int i=0;i<55;i++)
for(int j=0;j<55;j++)
for(int k=0;k<55;k++)
dp[i][j][k]=-999999999.0;
dp[0][0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=n1;j++)
for(int k=0;k<=n2;k++)
{
if(j+k>i) break;
dp[i][j][k]=dp[i-1][j][k];
if(j==0&&k==0);
else if(k==0) dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+data[i].q1-data[i].c1*r);
else if(j==0)dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+data[i].q2-data[i].c2*r);
else
{
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+data[i].q1-data[i].c1*r);
dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+data[i].q2-data[i].c2*r);
}
}
}
return dp[n][n1][n2]>0.0;
}
int main()
{
intT;
scanf("%d",&T);
for(int cas=1;cas<=T;cas++)
{
memset(dp,0,sizeof(dp));
scanf("%d%d%d",&n,&n1,&n2);
for(int i=1;i<=n;i++) data[i].input();
double l=0.0,r=20000.0;
while(r-l>0.000001)
{
double mid=(l+r)/2.0;
if(isok(mid))l=mid;
else r=mid;
}
printf("Case #%d: %.6lf\n",cas,l);
}
return 0;
}