重庆2019acm市赛
A. 狂小P猜密码
思路
从题意可知,十个数中有 \(1-4\) 个 \(1\) 时是存在方案的,其他情况答案都是 \(0\)。
已知一多重集,共 \(n\) 个数,其中 \(n_1\) 个 \(a_1\),\(n_2\) 个 \(a_2\),...,\(n_k\) 个 \(a_k\)。其全排列数为:
可知,当有 \(1\) 个 \(1\) 时,答案为 \(1\)。
当有 \(2\) 个 \(1\) 时,可能是一个数出现 \(3\) 次,另一个数出现 \(1\) 次;也可能是两个数都出现两次。答案为:
当有 \(3\) 个 \(1\) 时,有一个数出现两次,另外两个数出现 \(1\) 次。答案为:
当有 \(4\) 个 \(1\) 时,四个数各出现一次,答案为 \(4!=24\) 。
代码
#include <bits/stdc++.h>
using namespace std;
int main(){
int num=0;
for(int i=1,x;i<=10;i++){
scanf("%d",&x);
num+=x;
}
if(num==1) printf("1");
else if(num==2) printf("14");
else if(num==3) printf("36");
else if(num==4) printf("24");
else printf("0");
return 0;
}
B. cyy的珍珠奶茶
思路
设大圆半径为 \(R\),小圆半径为 \(r\),有 \(n\) 个小圆。
如果 \(n=1\),\(r=R\)
如果 \(n=2\),\(r=\frac{R}{2}\)
如果 \(n\geq 3\),看图
代码
#include <bits/stdc++.h>
using namespace std;
int T;
double r,n;
int main(){
scanf("%d",&T);
while(T--){
scanf("%lf%lf",&r,&n);
if(n==1) {printf("%lf\n",r);continue;}
if(n==2) {printf("%lf\n",r/2);continue;}
double a=3.1415926535/n;
double ans=sin(a)/(1+sin(a))*r;
printf("%lf\n",ans);
}
return 0;
}
F. JiangYu的机器人
签到题
代码
#include <bits/stdc++.h>
using namespace std;
int T,n,x,y,k;
int main(){
scanf("%d",&T);
while(T--){
scanf("%d%d%d%d",&n,&k,&x,&y);
if((y-1)%k==0||(n-y)%k==0||(y-x)%k==0)
printf("YES\n");
else printf("NO\n");
}
return 0;
}
G. cyy买奶茶
思路
01分数规划+树形Dp
可以发现题目给了一颗 \(n+1\) 个节点的树,编号从 \(0\) 到 \(1\)。
要求从 \(0\) 走出,要最后要回到 \(0\)。可知,走过的每条边累计走两次。
设第 \(i\) 个节点点权 \(a_i\),连接其父节点的边的边权为 \(b_i\)。
那么本题要求的就是 \(\frac{\sum_{i=1}^{n}x_i\cdot a_i}{\sum_{i=1}^{n}x_i\cdot 2\cdot b_i}\ (x_i=0\ 或\ 1)\) 的最大值
这就是一个典型的01分数规划的问题,解法是用二分,具体可以百度
然而这个题中每个节点之间是有依赖关系的。
那么二分的验证就是一个树形Dp,每个节点都要取到最优的价值。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
int n,head[MAXN],to[MAXN*2],nxt[MAXN*2],val[MAXN*2],tot,c[MAXN];
void add(int u,int v,int w){
to[++tot]=v;val[tot]=w;nxt[tot]=head[u];head[u]=tot;
}
double sum[MAXN];
void dfs(int u,int fa,int len,double L){
for(int i=head[u];i;i=nxt[i])
if(to[i]!=fa)
dfs(to[i],u,val[i]*2,L);
sum[u]=1.0*c[u]-L*len;
for(int i=head[u];i;i=nxt[i])
if(to[i]!=fa&&sum[to[i]]>0)
sum[u]+=sum[to[i]];
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1,u,v,w;i<=n;i++){
scanf("%d%d%d",&u,&v,&w);
add(u,v,w);add(v,u,w);
}
double l=0,r=1e9;
while(r-l>1e-8){
double mid=(l+r)/2;
dfs(0,0,0,mid);
if(sum[0]>0) l=mid;
else r=mid;
}
printf("%.6lf\n",l);
return 0;
}
H. lajiniunai的黑山羊
思路
贪心+带权并查集
首先贪心是很好想到的,将羊群按 \(b\) 的大小从大到小排序,从前到后依次决定每个羊的 \(a\) 值,如果当前 \(a\) 已有羊占用,就 \(+1\) 再看。当然这样的花费是最少的,但这样慢慢自加肯定会超时。
用带权并查集可以加速这个自加过程,将连续的用过的数合并到一个集合,如果遇到一个已经用过的数,可以通过并查集直接跳到这个数所在集合的末尾,然后计算花费。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1e5+10;
typedef long long LL;
int T,n,fa[MAXN],siz[MAXN],minv[MAXN];
map<int,int> mp;
struct Pair{
int a,b;
}p[MAXN];
bool cmp(Pair x,Pair y){
return x.b>y.b;
}
int get(int x){
if(x==fa[x]) return x;
int root=get(fa[x]);
minv[fa[x]]=min(minv[x],minv[fa[x]]);
return fa[x]=root;
}
void merge(int x,int y){
x=get(x),y=get(y);
fa[x]=y;
minv[y]=min(minv[x],minv[y]);
siz[y]+=siz[x];
}
int main(){
scanf("%d",&T);
while(T--){
mp.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d%d",&p[i].a,&p[i].b);
for(int i=1;i<=n;i++)
fa[i]=i,siz[i]=1;
sort(p+1,p+n+1,cmp);
LL ans=0;
for(int i=1;i<=n;i++){
int num;
if(!mp[p[i].a]){
num=p[i].a;
}
else{
int rt=get(mp[p[i].a]);
num=minv[rt]+siz[rt];
ans+=1ll*(num-p[i].a)*p[i].b;
}
mp[num]=i;
minv[i]=num;
if(mp[num-1]) merge(mp[num-1],i);
if(mp[num+1]) merge(mp[num+1],i);
}
printf("%lld\n",ans);
}
return 0;
}
未完待续……