大致情况
满分: 300 p t s 300pts 300pts
实际得分: 180 p t s 180pts 180pts
失误: 30 p t s 30pts 30pts
T1 SOJ2197
给定一个正整数 S S S,现在要求你选出若干个互不相同的正整数,使得它们的和不大于 S S S,而且每个数的因数(不包括本身)之和最大。 ( S ≤ 1000 ) (S\le1000) (S≤1000)
预处理:每个数约数和 s u m [ i ] sum[i] sum[i],时间复杂度 O ( S 2 ) O(S^2) O(S2)
法1:01背包
for(int i=1;i<=n;i++){
for(int j=n;j>=i;j--){
f[j]=max(f[j],f[j-i]+sum[i]);
}
}
答案: f [ n ] f[n] f[n]
法2:dfs+剪枝
void dfs(int nw,int s,int cnt){
if(cnt<f[nw]) return ;
f[nw]=cnt;
if(nw+s+1>n) return ;
for(int i=s+1;i<=n-nw;i++){
dfs(nw+i,i,cnt+sum[i]);
}
}
答案: m a x ( f [ i ] ) , i ∈ [ 1 , n ] max(f[i]),i∈[1,n] max(f[i]),i∈[1,n]
死因
s u m [ 1 ] = 0 而 并 非 1 sum[1]=0而并非1 sum[1]=0而并非1
100 p t s ⟶ 70 p t s 100pts\longrightarrow 70pts 100pts⟶70pts
T2 SOJ2198
给一个长度为 n n n 的数组 A A A,给定 L , R L, R L,R,求所有满足长度大等于 L L L,小等于 R R R 的 A A A 数组的子区间的平均值的最大值。 ( n ≤ 1 0 5 ) (n\le10^5) (n≤105)
做法:二分答案+单调队列
二分平均值 m i d mid mid,将 A A A数组每个数减去 m i d mid mid,再判断是否有一段长度在 [ L , R ] [L,R] [L,R]间的区间和大于 0 0 0,用单调队列判断。
bool check(double x){
for(int i=1;i<=n;i++){
b[i]=a[i]-x;
sum[i]=sum[i-1]+b[i];
}
int head=1,tail=0;
double ans=0;
for(int i=1;i<=n;i++){
while(head<=tail&&i-q[head]>R){
++head;
}
if(i>=L){
while(head<=tail&&sum[q[tail]]>sum[i-L]){
--tail;
}
q[++tail]=i-L;
ans=max(ans,sum[i]-sum[q[head]]);
}
}
return ans>0;
}
T3 SOJ2199
给定 m m m条方向不确定的有向边,让你选出一些边并确定它们的方向,使每个点的入度刚好为 1 1 1,并使边权之和最小。 ( m ≤ 5 ∗ 1 0 5 ) (m\le5*10^5) (m≤5∗105)
做法
容易发现如果所有边均为无向边,那么我们的图将会由至少一颗基环树组成。所以我们的目标就变为找最小的基环树森林。
我们用最小生成树的思想,从小到大加边。
在加边时,我们考虑其两个端点是否连通,如果连通且该连通块无环,那么加入这条边后一定会出现一个环。如果不连通,那么通过两边的两个连通块是否有环来判断新的连通块是否有环。
for(int i=1;i<=m;i++){
e[i].x=Read(),e[i].y=Read(),e[i].w=Read();
q.push(e[i]);
}
while(!q.empty()){
Edge u=q.top();
q.pop();
int x=findfa(u.x),y=findfa(u.y);
if(x==y){
if(!type[x]) type[x]=1,ans+=u.w,cnt++;
}
else{
if(type[x]&&type[y]) continue;
fa[x]=y; type[y]=type[y]|type[x];
ans+=u.w; cnt++;
}
}
if(cnt!=n) cout<<"No\n";
else cout<<ans<<endl;
死因
没有考虑整个图不连通的情况。