T1
description
给定一个正整数 S,现在要求你选出若干个互不相同的正整数,使得它们的和不大于 S,而
且每个数的因数(不包括本身)之和最大。
input
输入一个数 S。
output
输出一个数 ans
样例
sample input 1
4
1.5 sample output 1
3
1.6 sample input 2
6
1.7 sample output 2
6
data range
对于 30% 的数据,S ≤ 10。
对于 100% 的数据,S ≤ 1000。
思路
典型背包问题
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
int s;
int sum[1005];
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-') f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
return cnt*f;
}
signed main(){
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
s=read();
for(int i=1;i<=s;i++){
for(int j=1;j<=i/2;j++){//任何一个因数都不会超过i/2
if((i%j)==0){
sum[i]+=j;//预处理出因数和
}
}
}
for(int i=1;i<=s;++i){//枚举当前
for(int j=1;j<=i/2;++j){
sum[i]=max(sum[i],sum[j]+sum[i-j]);//sum类似于前缀和
}
}
printf("%lld",sum[s]);
return 0;
}
T2
思路:二分+单调队列优化
单调队列一般是具有单调性的队列,有单调递增和单调递减两种,一般来讲,队列的队首是整个队列的最大值或最小值
实现:若队列为空,将A[i]从队尾入队
若队列不为空,将比A[i]大的元素都从队尾弹出,然后把A[i]入队
若队列不为空且A[i]大于队尾,则直接从队尾把A[i]入队
代码
sum:减去平均值后的前缀和
check中枚举
- 注意i是枚举的右端点
- 与 枚举左端点所在的区间
-
单增队列维护序列中最小的前缀和
值
注意 单调队列维护仅适用于只考虑最大最小值,弹出队列状态都对答案不产生任何影响时(决策单调性)
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+50;
int n,L,R,a[N];
double mid;
inline int read()
{
char ch = getchar();int i = 0, f = 1;
while (!isdigit(ch)){if (ch == '-')f = -1;ch = getchar();}
while (isdigit(ch)){i = (i << 1) + (i << 3) + ch - '0';ch =getchar();}
return i * f;
}
bool check(double v){
static double sum[N],que[N];
static int head,tail,pos[N];
head=1;tail=0;
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+1.0*a[i]-v;
}
for(int i=L;i<=n;i++){
while(head<=tail&&que[tail]>=sum[i-L]){
tail--;//如果单调队列最末大于现在值,不断出队以维护单调性
}
while(head<=tail&&pos[head]+R<i){//pos[head]<i-R此时已经不可能作为左端点,出队
head++;
}
que[++tail]=sum[i-L];
pos[tail]=i-L;
if(sum[i]-que[head]>=0){
return true;
}
}
return false;
}
int main(){
n=read(),L=read(),R=read();
for(int i=1;i<=n;++i){
a[i]=read();
}
double l=0,r=1e6;
for(int i=1;i<=100;i++){
mid=(l+r)/2;
if(check(mid)){
l=mid;
}
else r=mid;
}
printf("%.4lf\n",mid);
return 0;
}
T3
题目[华莱士]
思路
判断是否能加入一条边时,如果这条边的两端已经联通,我们需要知道这个联通块是否有环。如果不连通,那么可以加入这条边,而且通过两端的联通块是否有环可以得到新的联通块是否有环。
用并查集维护连通性,额外维护联通块里是否有环即可。
- 边的两端是同一棵树:判环;有环:不连;没环:连
- 边的两端是两棵树:判环;两个都有环:不连;其余:连
代码
book数组记录有没有环
fa维护并查集
tot连的边数
树上还要带一条边,故不是
并查集合并时合并环的信息
#include <bits/stdc++.h>
using namespace std;
int fa[500050],book[500050];
struct node{
int x,y,w;
}a[500050];
bool cmp(const node &a,const node &b){
return a.w<b.w;
}
inline char nc(){//超级快读
static char buf[100000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int _read(){
char ch=nc();int sum=0;
while(!(ch>='0'&&ch<='9'))ch=nc();
while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
return sum;
}
int getf(int x){
return fa[x]==x?x:fa[x]=getf(fa[x]);
}
int main(){
int n,m,i,x,y;
scanf("%d%d",&n,&m);
for(i=1;i<=n;++i) fa[i]=i;
for(i=1;i<=m;++i) scanf("%d%d%d",&a[i].x,&a[i].y,&a[i].w);
sort(a+1,a+m+1,cmp);//按边权排序
long long tot=0,ans=0;
for(int i=1;i<=m;i++){
x=getf(a[i].x),y=getf(a[i].y);
if(x==y&&!book[x]){
book[x]=1;tot++;ans+=a[i].w;
}
else{
if(book[x]&&book[y]) continue;
fa[x]=y;book[y]=book[x]|book[y];
tot++;ans+=a[i].w;
}
}
if(tot!=n){
printf("No");
}
else{
printf("%lld",ans);
}
return 0;
}