1. 程式人生 > >[USACO 2018 Open Gold] Tutorial

[USACO 2018 Open Gold] Tutorial

push_back index.php 算法 urn turn 分答 lse ora pac

Link:

傳送門

A:

對於每一條分割線,設本不應在其左側的個數為$x$

重點要發現每次一來一回的操作恰好會將一對分別應在左/右側的一個數從右/左移過去

這樣就轉直接用樹狀數組求出最大的$x$即可

技術分享圖片
#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second
typedef long long ll;
typedef pair<int,int> P;
const int MAXN=1e5+10;
//res至少為1 
int n,bit[MAXN],res=1;P dat[MAXN];

void Update(int x) {while(x<=n) bit[x]++,x+=x&(-x);} int Query(int x) { int ret=0; while(x) ret+=bit[x],x-=x&(-x); return ret; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&dat[i].X),dat[i].Y=i; sort(dat+1,dat+n+1); for
(int i=1;i<n;i++) Update(dat[i].Y),res=max(res,i-Query(i)); printf("%d",res); return 0; }
Problem A

冒泡排序相關題多考慮數與數相對位置以及每個數應在的位置

B:

發現答案具有單調性後首先想到二分答案或是直接枚舉

由於不用重復建圖,直接枚舉看起來能更快一些,不過好像並不能在線$O(logn)$判斷是否出現了環……

(按照楊主力的指示還是在google上搜到了論文,不過並不打算學,傳送門)

剩下就是優先隊列套拓撲排序就行了

技術分享圖片
#include <bits/stdc++.h>

using
namespace std; #define X first #define Y second #define pb push_back typedef long long ll; typedef pair<int,int> P; typedef double db; const int MAXN=1e5+10; vector<int> dat[MAXN]; struct edge{int nxt,to;}e[MAXN<<2]; int n,m,num,x,head[MAXN],res[MAXN],in[MAXN],tot; void add_edge(int x,int y) {e[++tot].nxt=head[x];e[tot].to=y;head[x]=tot;} void build(int x) { tot=0; memset(in,0,sizeof(in)); memset(head,0,sizeof(head)); for(int i=1;i<=x;i++) for(int j=0;j<dat[i].size()-1;j++) add_edge(dat[i][j],dat[i][j+1]),in[dat[i][j+1]]++; } bool check() { int sz=0; priority_queue<int,vector<int>,greater<int> > q; for(int i=1;i<=n;i++) if(!in[i]) q.push(i); while(!q.empty()) { int t=q.top();q.pop(); res[++sz]=t; for(int i=head[t];i;i=e[i].nxt) { in[e[i].to]--; if(!in[e[i].to]) q.push(e[i].to); } } return (sz==n); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d",&num); for(int j=1;j<=num;j++) scanf("%d",&x),dat[i].pb(x); } int l=1,r=m; while(l<=r) { int mid=(l+r)>>1; build(mid); if(check()) l=mid+1; else r=mid-1; } build(r);check(); for(int i=1;i<=n;i++) printf("%d ",res[i]); return 0; }
Problem B

C:

明顯0/1分數規劃,每次$dp$找當前重量大於等於$m$的最大權值和即可

一篇很好的博客,圖解0/1分數規劃:傳送門

順便學了下$Dinkelbach$算法,發現就是每次找到最大值時直接跳到該方案的零點處

直到該方案的零點為當前$x$下的最大值時停止即可

技術分享圖片
#include <bits/stdc++.h>

using namespace std;
#define X first
#define Y second 
typedef long long ll;
typedef pair<int,int> P;
typedef double db;
const int MAXN=1e5+10;
int n,m,w[MAXN],t[MAXN];
db l,r,a[MAXN],dp[MAXN];

bool check(db x)
{
    for(int i=1;i<=n;i++)
        a[i]=t[i]-1.0*x*w[i];
    for(int i=1;i<=m;i++) dp[i]=-1e9;    
    
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
            dp[min(m,j+w[i])]=max(dp[min(m,j+w[i])],dp[j]+a[i]);
    return dp[m]>=0;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d%d",&w[i],&t[i]),r+=t[i];
    
    while(fabs(l-r)>1e-6)
    {
        db mid=(l+r)/2.0;
        if(check(mid)) l=mid;
        else r=mid;
    }
    printf("%d",int((l+r)*500));
    return 0;
}
Problem C

[USACO 2018 Open Gold] Tutorial