• NOIP2018


    铺设道路

    Link
    一个非常可行的做法是最小值分治。
    正解就是一个贪心:从前往后扫,如果当前这个坑比前面那个深,那么就消耗当前深度减前面的深度的代价,否则当前这个会被直接覆盖,不需要代价。

    #include<bits/stdc++.h>
    using namespace std;
    int n,a,last,ans;
    int main()
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a;
            if(a>last) ans+=(a-last);
            last=a;
        }
        cout<<ans;
        return 0;
    }
    

    货币系统

    Link
    先升序排序,然后从小到大枚举,如果当前的值能被之前的表示就跳过,否则答案加一。
    具体实现完全背包就行了。

    #include<bits/stdc++.h>
    using namespace std;
    int read(){int x;scanf("%d",&x);return x;}
    const int M=25001,N=101;
    int f[M],a[N];
    int main()
    {
        for(int T=read(),n,ans,i,j;T;--T)
        {
            memset(f,0,sizeof f),n=read(),ans=0;
    	for(i=1;i<=n;++i) a[i]=read();
    	sort(a+1,a+n+1),f[0]=1;
    	for(i=1;i<=n;++i) if(!f[a[i]]) for(++ans,j=a[i];j<=a[n];++j) f[j]|=f[j-a[i]];
            printf("%d
    ",ans);
        }
    }
    

    赛道修建

    Link
    先二分一个长度(lim)
    然后每个点开一个multiset记录子树中传上来的链的长度。
    (le lim)的部分可以直接统计进答案。
    其它的我们从小到大枚举,二分找到最小的满足两个加起来(le lim)的,然后把两个统计进答案。
    然后把集合中剩下的最长的链加上自己到父亲的距离传给父亲。
    这个贪心的正确性是显然的。

    #include<bits/stdc++.h>
    #define pi pair<int,int>
    #define pb push_back
    using namespace std;
    int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;}
    const int N=50007;
    vector<pi>E[N];int n,m,lim,ans;
    int dfs(int u,int fa)
    {
        multiset<int>s;
        for(auto[v,w]:E[u]) if(v^fa) s.insert(dfs(v,u)+w);
        for(auto it=s.lower_bound(lim);it!=s.end();++it,s.erase(prev(it))) ++ans;
        for(auto it=s.begin();it!=s.end();)
        {
    	auto p=s.lower_bound(lim-*it);
    	if(p==it) ++p;
    	if(p==s.end()){++it;continue;}
    	++ans,s.erase(p),++it,s.erase(prev(it));
        }
        return s.size()? *prev(s.end()):0;
    }
    int check()
    {
        dfs(1,ans=0);
        return ans>=m;
    }
    int main()
    {
        n=read(),m=read();int i,u,v,w,l=1,r=0;
        for(i=1;i<n;++i) u=read(),v=read(),r+=w=read(),E[u].pb(pi(v,w)),E[v].pb(pi(u,w));
        for(r/=m;l<=r;) lim=l+r>>1,check()? l=lim+1:r=lim-1;
        printf("%d",r);
    }
    

    旅行

    Link
    (O(n^2))的做法还是不难想的,基环树的情况我们发现一定会有一条边不走。
    先给边排个序,枚举删哪条边然后做就好了。
    需要一定的剪枝/优化。这里就不写了。

    #include<bits/stdc++.h>
    #define pi pair<int,int>
    #define pb push_back
    using namespace std;
    int read(){int x;scanf("%d",&x);return x;}
    const int N=5007;
    vector<pi>E[N];int ans[N],tmp[N],vis[N],ban,cnt;
    void dfs(int u){vis[tmp[++cnt]=u]=1;for(auto[v,id]:E[u]) if(id^ban&&!vis[v]) dfs(v);}
    void upd(int n)
    {
        if(cnt^n)return;
        for(int i=1;i<=n;++i) if(tmp[i]<ans[i]) return swap(tmp,ans); else if(tmp[i]>ans[i]) return ;
    }
    int main()
    {
        int n=read(),m=read(),i,u,v;memset(ans,0x3f,sizeof ans);
        for(i=1;i<=m;++i) u=read(),v=read(),E[u].pb(pi(v,i)),E[v].pb(pi(u,i));
        for(i=1;i<=n;++i) sort(E[i].begin(),E[i].end());
        if(m==n-1){dfs(1);for(i=1;i<=n;++i) printf("%d ",tmp[i]);return 0;}
        for(i=1;i<=n;++i) memset(vis,0,sizeof vis),ban=i,cnt=0,dfs(1),upd(n);
        for(i=1;i<=n;++i) printf("%d ",ans[i]);
    }
    

    填数游戏

    Link
    打表题,不想说啥了。

    #include<bits/stdc++.h>
    using namespace std;
    inline int read()
    {
        register int x=0;
        register char ch=getchar();
        while(ch<'0'||ch>'9')
    	ch=getchar();
        while(ch>='0'&&ch<='9')
    	x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
        return x;
    }
    const int p=1e9+7;
    inline int power(register int a,register int b){register int r=1;for(;b;b>>=1,a=1ll*a*a%p)if(b&1)r=1ll*a*r%p;return r;}
    int main()
    {
        register int n=read(),m=read();
        if(n>m)
    	swap(n,m);
        switch(n)
        {
        case 1:
    	return cout<<power(2,m),0;
        case 2:
    	return cout<<4ll*power(3,m-1)%p,0;
        case 3:
    	return cout<<112ll*power(3,m-3)%p,0;
        case 4:
    	if(m==4)
    	    return cout<<912,0;
    	else
    	    return cout<<2688ll*power(3,m-5)%p,0;
        case 5:
    	if(m==5)
    	    return cout<<7136,0;
    	else
    	    return cout<<21312ll*power(3,m-6)%p,0;
        case 6:
    	if(m==6)
    	    return cout<<56768,0;
    	else
    	    return cout<<170112ll*power(3,m-7)%p,0;
        case 7:
    	if(m==7)
    	    return cout<<453504,0;
    	else
    	    return cout<<1360128ll*power(3,m-8)%p,0;
        case 8:
    	if(m==8)
    	    return cout<<3626752,0;
    	else
    	    return cout<<10879488ll*power(3,m-9)%p,0;
        }
    }
    

    保卫王国

    Link
    DDP这东西以后有空再说吧。
    首先我们有一个(O(nm))的dp。
    (f_{u,0/1})表示覆盖(u)的子树,(u)不选/选的最小代价。
    然后再处理一个(g_{u,0/1})表示覆盖(u)的子树外,(u)不选/选的最小代价。这个形式的东西经常在换根dp里出现。
    那么对于一次询问,我们把((u,v))这条链拿出来,链上面的单独跑一遍,其它的子树是不会受到影响的,可以直接拿(f,g)来算。
    然后我们发现这个东西是可以倍增优化的,设(h_{u,i,0/1,0/1})表示覆盖除了(u)子树以外的(fa_{u,i})(u)(2^i)级祖先)的子树,(u)(fa_{u,i})分别不选/选的最小代价。
    然后每次询问倍增查询一下就行了。

    #include<bits/stdc++.h>
    #define pb push_back
    #define ll long long
    using namespace std;
    namespace IO
    {
        char ibuf[(1<<21)+1],obuf[(1<<21)+1],st[11],*iS,*iT,*oS=obuf,*oT=obuf+(1<<21);
        char Get(){return (iS==iT? (iT=(iS=ibuf)+fread(ibuf,1,(1<<21)+1,stdin),(iS==iT? EOF:*iS++)):*iS++);}
        void Flush(){fwrite(obuf,1,oS-obuf,stdout),oS=obuf;}
        void Put(char x){*oS++=x;if(oS==oT)Flush();}
        int read(){int x=0,c=Get();while(!isdigit(c))c=Get();while(isdigit(c))x=x*10+c-48,c=Get();return x;}
        void write(ll x){int top=0;while(x)st[++top]=(x%10)+48,x/=10;while(top)Put(st[top--]);Put('
    ');}
    }
    using namespace IO;
    ll min(ll a,ll b){return a<b? a:b;}
    void swap(int &a,int &b){a^=b^=a^=b;}
    const int N=100007;const ll inf=1e18;
    int n,val[N],dep[N],fa[N][17];ll f[N][2],g[N][2],h[N][17][2][2],x[2][2];vector<int>E[N];
    void dfs(int u,int Fa)
    {
        fa[u][0]=Fa,dep[u]=dep[Fa]+1,f[u][1]=val[u];
        for(int i=1;i<=16;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
        for(int v:E[u]) if(v^Fa) dfs(v,u),f[u][0]+=f[v][1],f[u][1]+=min(f[v][0],f[v][1]);
    }
    void dfs(int u){for(int v:E[u]) if(v^fa[u][0]) g[v][0]=g[u][1]+f[u][1]-min(f[v][0],f[v][1]),g[v][1]=min(g[u][0]+f[u][0]-f[v][1],g[v][0]),dfs(v);}
    void init()
    {
        int i,j,k,l;
        for(i=1;i<=n;++i) h[i][0][0][0]=inf,h[i][0][1][1]=h[i][0][0][1]=f[fa[i][0]][1]-min(f[i][0],f[i][1]),h[i][0][1][0]=f[fa[i][0]][0]-f[i][1];
        for(j=1;j<=16;++j) for(i=1;i<=n;++i) for(k=0;k<=1;++k) for(l=0;l<=1;++l) h[i][j][k][l]=min(h[i][j-1][k][0]+h[fa[i][j-1]][j-1][0][l],h[i][j-1][k][1]+h[fa[i][j-1]][j-1][1][l]);
    }
    void upd(int u,int i,int id)
    {
        ll t0=min(x[id][0]+h[u][i][0][0],x[id][1]+h[u][i][1][0]),t1=min(x[id][0]+h[u][i][0][1],x[id][1]+h[u][i][1][1]);
        x[id][0]=t0,x[id][1]=t1;
    }
    ll cal(int u,int a,int v,int b)
    {
        if(dep[u]<dep[v]) swap(a,b),swap(u,v);
        x[0][a]=f[u][a],x[1][b]=f[v][b],x[0][!a]=x[1][!b]=inf;
        for(int i=16;~i;--i) if(dep[fa[u][i]]>=dep[v]) upd(u,i,0),u=fa[u][i];
        if(u==v) return x[0][b]+g[u][b];
        for(int i=16;~i;--i) if(fa[u][i]^fa[v][i]) upd(u,i,0),upd(v,i,1),u=fa[u][i],v=fa[v][i];
        int o=fa[u][0];
        return min(f[o][0]-f[u][1]-f[v][1]+x[0][1]+x[1][1]+g[o][0],f[o][1]-min(f[u][0],f[u][1])-min(f[v][0],f[v][1])+min(x[0][0],x[0][1])+min(x[1][0],x[1][1])+g[o][1]);
    }
    void solve()
    {
        int a=read(),x=read(),b=read(),y=read();
        if(!x&&!y&&(fa[a][0]==b||fa[b][0]==a)) return Put('-'),Put('1'),Put('
    ');
        write(cal(a,x,b,y));
    }
    int main()
    {
        n=read();int Q=read(),i,u,v;read();
        for(i=1;i<=n;++i) val[i]=read();
        for(i=1;i<n;++i) u=read(),v=read(),E[u].pb(v),E[v].pb(u);
        dfs(1,0),dfs(1),init();
        while(Q--) solve();
        return Flush(),0;
    }
    
  • 相关阅读:
    POJ 1673
    POJ 1375
    POJ 1654
    POJ 1039
    POJ 1066
    UVA 10159
    POJ 1410
    POJ 2653
    POJ 2398
    POJ 1556
  • 原文地址:https://www.cnblogs.com/cjoierShiina-Mashiro/p/11850330.html
Copyright © 2020-2023  润新知