ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
题目链接:http://codeforces.com/contest/1079
官方题解:http://codeforces.com/blog/entry/63324
A题
题意:吃一种食物需要完整的一套餐具,每套餐具是是一样的,由不同的餐具组成,如刀子,叉子,筷子(由不同的数字表示,如1,2,3)。用餐过程会被偷掉一些餐具。现在知道最后剩下n个餐具,分别为a[1],a[2],a[3]…a[n],和k个客人,问最少被偷掉几个餐具
解法:最主要的点是算出食物的数目(fish),跟据餐具的套数算出最少的fish
fish的最少为 餐具套数/人数 (向上取整)
a
n
s
=
s
u
m
∗
f
i
s
h
∗
k
−
n
ans = sum*fish*k-n
ans=sum∗fish∗k−n (sum为一套餐具最少有几种餐具)
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,k,a[200];
int main(){
cin>>n>>k;
int x;
fo(i,1,n){
scanf("%d",&x);
a[x]++;
}
int mx= -1,sum=0;
fo(i,1,100){
if(a[i]) mx = max(mx,a[i]),sum++;
}
if(mx%k==0)mx/=k;
else mx = mx/k +1;
int ans = sum*mx*k - n;
cout<<ans;
return 0;
}
B题
题意:问一个字符串最少能分成几行,每列最多20个字符。形成一个矩阵,不为矩阵由*代替,两行差不能超过2个星
解法:算出最少有多少行,跟据行算出列,算出有多少个星星,输出,每行最多一个星星
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
char s[106];
int main(){
scanf("%s",s+1);
int len = strlen(s+1);
int row = len%20==0? len/20:len/20+1;
int col = len%row==0? len/row:len/row+1;
int xin = row * col - len;
int t1 = 0, t2=1;
cout<<row<<" "<<col<<endl;
fo(i,1,row){
int j = 1;
if(t1++<xin)printf("*"),j++;
for(j; j<=col; j++){
printf("%c",s[t2++]);
}
printf("\n");
}
}
C题
题意:给定一个数列a,求数列b,
1
<
=
b
[
i
]
<
=
5
1<=b[i]<=5
1<=b[i]<=5
i
f
a
[
i
]
<
a
[
i
+
1
]
t
h
e
n
b
[
i
]
<
b
[
i
+
1
]
if \ a[i]<a[i+1] \ then \ b[i]<b[i+1]
if a[i]<a[i+1] then b[i]<b[i+1]
i
f
a
[
i
]
>
a
[
i
+
1
]
t
h
e
n
b
[
i
]
>
b
[
i
+
1
]
if \ a[i]>a[i+1] \ then \ b[i]>b[i+1]
if a[i]>a[i+1] then b[i]>b[i+1]
i
f
a
[
i
]
=
a
[
i
+
1
]
t
h
e
n
b
[
i
]
≠
b
[
i
+
1
]
if \ a[i]=a[i+1] \ then \ b[i]≠b[i+1]
if a[i]=a[i+1] then b[i]̸=b[i+1]
解法:
记忆化搜索,O(5n)
d
p
[
s
t
e
p
]
[
i
]
=
x
dp[step][i]=x
dp[step][i]=x 表示第
s
t
e
p
−
1
step-1
step−1步的值为
x
x
x,
s
t
e
p
step
step步的值为$i $
d
f
s
(
s
t
e
p
,
x
)
dfs(step,x)
dfs(step,x) 表示第
s
t
e
p
−
1
step-1
step−1步的值为x是否合法,合法返回
s
t
e
p
−
1
step-1
step−1的值
#include<bits/stdc++.h>
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,dp[100005][6],a[100005];
// dp[step][i]=x 表示第step-1步的值为x,step步的值为i
// dfs(step,x) 表示第step-1步的值为x是否合法,合法返回step-1的值
bool dfs(int step, int x){// step当前,x属于step-1
if(step>n) return dp[step][0]=x;
if(dp[step][x]!=-1)return dp[step][x];
if(a[step]>a[step-1]){
fo(i,x+1,5){
if(dfs(step+1,i))return dp[step][i]=x; // 搜对了才赋值,相当于回溯,但显然比回溯快
} // 因为回溯会破坏掉之前已经搜索过的错误解空间,导致重判
}else if(a[step]<a[step-1]){
fo(i,1,x-1){
if(dfs(step+1,i))return dp[step][i]=x;
}
}else{
fo(i,1,5)if(i!=x){
if(dfs(step+1,i))return dp[step][i]=x;
}
}
return dp[step][x]=0; // 上个值x搞错了,往后都不合法
}
int b[100005];
int main(){
cin>>n;
fo(i,1,n)scanf("%d",&a[i]);
memset(dp,-1,sizeof(dp));
dfs(1,0); // 出口总返回0, 第0号元素的值
if(dp[n+1][0]!=-1){
int t=n;
for(int x=dp[n+1][0],i=n; i>=1; x=dp[i][x],i--){
b[t--]=x; // 往回赋值
}
fo(i,1,n)printf("%d%c",b[i],i==n?'\n':' ');
return 0;
}
puts("-1");
return 0;
}
D题
题意:给一个网格上的两个点a,b和一条直线 Ax+By+C=0。点a只能沿着网格或这直线走到b。问最小行走距离。
解法:枚举a不走直线到b。
a沿x轴走到直线到达点a1,a沿y轴走到直线到达点2
同理得b1,b2.
枚举 a --> a(1or2) --> b(1or2) --> b 的距离
取5种情况的最小值
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
double a,b,c,x1,y1,x2,y2,xa[6],ya[6],xb[6],yb[6];
double dist(double x1,double y1,double x2, double y2){
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
int main(){
cin>>a>>b>>c;
cin>>x1>>y1>>x2>>y2;
xa[1]=x1,ya[1]=-(a*xa[1]+c)/b; // a上下
ya[2]=y1,xa[2]=-(b*ya[2]+c)/a; // a左右
xb[1]=x2,yb[1]=-(a*xb[1]+c)/b; // b上下
yb[2]=y2,xb[2]=-(b*yb[2]+c)/a; // b上下
double ans = fabs(x1-x2)+fabs(y1-y2);
fo(i,1,2)fo(j,1,2){
ans = min(ans, fabs(xa[i]-x1)+fabs(ya[i]-y1)+fabs(xb[j]-x2)+fabs(yb[j]-y2)+dist(xa[i],ya[i],xb[j],yb[j]));
}
printf("%.10f\n",ans);
return 0;
}
E题
题意:把一种质量看做一种商品。给定一些商品的重量即数列a[1],[2],a[3]…a[n],但是同学1无法区分重量和商品,但是他可以问同学2:k个商品的重量和sum有哪些。同学2会指出哪k个商品和为sum(如果存在的话)。问同学1最多能区分多少个商品,也就是说具体知道哪些商品的重量。
解法:由于同学1太傻了,所以同学2一但指出k个商品但是商品中有不同种类的,那么同学1还是无法得知哪个商品具体是哪个。
那么同学1只能询问同一种商品,k个了。
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示i个商品组成质量为j的组合数。
这里的组合数是这样的
如数
2
2
2
2
2
2
2
2
2
2
2
2
被算为 2 4 6 8 共4种组合 ,而不是01背包那样, 4 可以由不同的2组成,然后进行多次计算
即
f
[
1
]
[
2
]
=
1
,
f
[
2
]
[
4
]
=
1
,
f
[
3
]
[
6
]
=
1
,
f
[
4
]
[
8
]
=
1
f[1][2] = 1, f[2][4] = 1, f[3][6] = 1, f[4][8] = 1
f[1][2]=1,f[2][4]=1,f[3][6]=1,f[4][8]=1
那么问题转换为:
同学1询问同一种商品(同一种质量),其质量为
a
[
i
]
a[i]
a[i],数目为
k
k
k个,和为
s
u
m
=
k
∗
a
[
i
]
sum=k*a[i]
sum=k∗a[i]。
f
[
k
]
[
s
u
m
]
=
1
f[k][sum] = 1
f[k][sum]=1;求最大的
k
k
k
所以类似做 多重背包一样
但是枚举背包重量变成枚举已有的组合
f
[
0
]
[
0
]
=
1
f[0][0] = 1
f[0][0]=1 边界
f
[
i
+
s
i
z
e
]
[
j
+
s
i
z
e
∗
a
[
i
]
]
+
=
f
[
i
]
[
j
]
f[i+size][j+size*a[i]] += f[i][j]
f[i+size][j+size∗a[i]]+=f[i][j] (
s
i
z
e
size
size表示选中了几个
a
[
i
]
a[i]
a[i])
注意:若只有2种质量,那么同学1能区别所有的商品和重量
map做法:
#include<bits/stdc++.h>
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int N, a[105],all,cnt[105];
unordered_map<int,int> f[105];
void solve(){
f[0][0]=1;
fo(x,1,100)if(cnt[x]){ // 访问每个物品
for(int i=99; i>=0; i--)for(auto p:f[i]){// 从后往前访问所有组合,保证每次使用一次有重物品,枚举已有背包
fo(t,1,cnt[x]){// 访问物品的数目
if(t+i>100)break;
f[i+t][p.first+t*x] += p.second;// f[数目][值],x.first为值,x.second为totc[数目][值]的值,即组合数的多少
//f[i][f]一定没有用过该物品,所以这次加上的数只用过一次这种物品
}
}
}
int ans = 1;
fo(i,1,100)fo(size,1,cnt[i]){
if(f[size][i*size]==1)ans = max(ans, all==2?N:size);
}
cout<<ans;
}
int main(){
scanf("%d",&N);
fo(i,1,N){
scanf("%d",&a[i]);
cnt[a[i]]++;
if(cnt[a[i]]==1)all++;
}
solve();
return 0;
}
数组做法
#include<bits/stdc++.h>
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; i++)
using namespace std;
int n,a[105],cnt[105],mx,size,all;
int f[105][10505]; // f[i][j]
int zero_one(){
f[0][0]=1;
fo(x,1,100)if(cnt[x])
for(int i=n;i>=0;i--)fo(j,0,mx)if(f[i][j]){ // 从后往前枚举已有背包
fo(t,1,cnt[x]){ // 递增序列,2,4,6,8...每个数对一个背包只使用一次
if(i+t>100||j+x*t>10000)break;
f[i+t][j+x*t] += f[i][j];
// printf("dp[%d][%d]=%d\n",i+t,j+x*t,f[i+t][j+x*t]);
}
}
}
int c[101],num[101];
/* 错误示例
void bage(){
int t = 0;
fo(i,1,101)fo(j,1,cnt[i]){
c[++t] = i*j;
num[t] = j;
}
f[0][0]=1;
for(int i=n; i>=0; i--)fo(j,0,mx)if(f[i][j]){//这种不行只跑了一趟.... 错误示例
fo(x,1,n){
if(i+num[x]>100||j+c[x]>10000)break;
f[i+num[x]][j+c[x]] += f[i][j];
printf("dp[%d][%d]=%d\n",i+1,j+c[x],f[i+num[x]][j+c[x]]);
}
}
}*/
void solve(){
int ans=1;
zero_one();
// bage();
fo(i,1,100){
fo(size,1,cnt[i]){
if(f[size][size*i]==1){// 只有一种组合,那么能区别size个数,
ans = max(ans, (all==2&&size==cnt[i]) ? n:size); // 若总共有两种数,那全能区别
}
}
}
cout<<ans;
}
int main(){
cin>>n;
fo(i,1,n){
scanf("%d",&a[i]),cnt[a[i]]++;
if(cnt[a[i]]==1)all++;
mx+=a[i];
}
solve();
}