Hnoi2013題解 bzoj3139~3144


話說好久沒寫題(解)了。。

先貼份題解:http://wjmzbmr.com/archives/hnoi-2013-%E9%A2%98%E8%A7%A3/(LJ神題解。。Lazycal表示看不懂。。)

以下是Lazycal's題解:

[bzoj3139][Hnoi2013]比賽

對於一個得分序列,可以發現不論如何排列,答案都是一樣的。而且n的得分序列可以由n-1的推來。於是,我們可以搜索第一個隊伍與其他隊伍的比賽結果,由比賽結果得出n-1支隊伍的得分序列,遞歸搜索。然后用每個隊伍的得分和隊伍個數作為狀態,對每個狀態進行壓縮,利用最小表示法。由於每個隊伍最多只能得27分,所以狀態總數不超過28^9/(9!)*2(因為只能存在一個0)為千萬級別,可以接受。 

 

代碼(………………將就着用吧,兩種代碼高亮的混合使用…………)

 

[bzoj3140][Hnoi2013]消毒

假設我們選擇x*y*z(x<y<z)這樣一塊區域,則費用為x。也就是說,我們可以視作x次的1*y*z。又因為a*b*c<=5000,所以min{a,b,c}<=17。不妨設min{a,b,c}=a。因此答案<=a。一開始還以為只要把需要消的1*b*c消了就可以了。結果去網上查了題解才知道這樣並不一定是最優的……

正解應當是用2^a枚舉哪個1*b*c要取,然后轉化成二維的最小棋盤覆蓋。最小棋盤覆蓋就是用最少的行或列把指定點覆蓋。沒錯,這個我也不會……

膜拜了abcdabcd987的題解后才明白。其實是最小點覆蓋的模型。只要把行和列拆開,作為二分圖的X和Y頂點,然后對於需要覆蓋的點從所屬的行連向列即可。

/**************************************************************
    Problem: 3140
    User: lazycal
    Language: C++
    Result: Accepted
    Time:1192 ms
    Memory:1320 kb
****************************************************************/
 
#include <cstdio>
#include <algorithm>
const int N=5000+9;
int ans,a,b,c,T,vis[N],match[N],num,son[N],ec,times;
bool t[N];
struct Edge{int link,next;}es[N*10];
inline void addedge(const int x,const int y)
{
    es[++ec].next=son[x];
    es[ec].link=y;
    son[x]=ec;
}
struct point
{
    int x,y,z;
    point(const int _x=0,const int _y=0,const int _z=0):x(_x),y(_y),z(_z)
    {
        if (a<=b && a<=c) std::swap(x,x);
        else if (b<=a && b<=c) std::swap(x,y);
        else std::swap(x,z);
    }
}one[N];
bool find(const int u)
{
    for (int i=son[u];i;i=es[i].next) {
        const int v=es[i].link;
        if (vis[v]==times) continue;
        vis[v]=times;
        if (!match[v]/*BUGS!!!*/ || find(match[v])) {
            match[v]=u;
            return 1;
        }
    }
    return 0;
}
void dfs(const int step,int cnt)
{
    if (cnt>=ans) return;
    if (step>a) {
        ec=0;
        for (int i=1;i<=b;++i) son[i]=0;
        for (int i=0;i<num;++i)
            if (!t[one[i].x]) addedge(one[i].y,one[i].z);
        for (int i=1;i<=c;++i) match[i]=0;
        for (int i=1;i<=b;++i) {
            ++times;
            if ((cnt+=find(i))>=ans) return;
        }
        ans=cnt;
        return;
    }
    t[step]=1;
    dfs(step+1,cnt+1);
    t[step]=0;
    dfs(step+1,cnt);
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("3140.in","r",stdin);
    freopen("3140.out","w",stdout);
    #endif
    scanf("%d",&T);
    while (T--) {
        scanf("%d%d%d",&a,&b,&c);
        num=0;
        for (int i=1;i<=a;++i)
            for (int j=1;j<=b;++j)
                for (int k=1,x;k<=c;++k) {
                    scanf("%d",&x);
                    if (x) one[num++]=point(i,j,k);
                }
        if (a<=b && a<=c) std::swap(a,a);
        else if (b<=a && b<=c) std::swap(a,b);
        else std::swap(a,c);
        ans=a;
        dfs(1,0);
        printf("%d\n",ans);
    }
}

  

[bzoj3141][Hnoi2013]旅行

這題非常神!的的確確是個好題!

這題我是看着http://tieba.baidu.com/p/2283515454?pid=31800289139&cid=0#318002891399樓的回復以及http://cxjyxx.me/?p=329的題解才懂的……表示自己太弱了……

注:以下a[i],b[i],n,m如題目所述。

首先我們考慮特殊情況,並將b[i]為0的項賦值為-1,令S為所有數之和:

  1. 全1。此時不難發現答案是|S|/m上取整。
  2. 全-1。同上,為|S|/m上取整。
  3. 一半1,一半-1。我們將較少的1or-1與另一邊抵消。如 1 1 1 1 1 -1 -1  ------>   1 1 1 (1 1 -1 -1),這樣就跟上面的大概一樣了。
  4. 其它情況也差不多。

猜測:ans=|S|/m上取整(S!=0)。

答案:

  1. ans>=|S|/m上取整,這很顯然。
  2. m<=|S|:不妨設S>0,則將所有的-1根附近的1消掉,最終便只剩1了,ans=|S|/m上取整。
  3. m>|S|:令s[i]=b[i]+b[i+1]+...+b[n]
    • S=0:
      • 若s[i]=0的個數>=m,則ans=0,此時取最后一個點和后m-1個s[i]=0的點即可。
      • 否則ans=1。因為若ans要為0,則所選的最后一個點必須滿足s[i]=0(因為s[i]即為最后的旅途),倒二個也是,依此類推,所選的點s[i]都為0。但s[i]=0的個數<m,所以ans>=1。數據構造:觀察s數組,必定是0 1……1 0……0 1……1 0。也就是像一個個小山一樣。每次取一條等高線,就可符合題意。
    • S!=0:我們首先選出|S|個數,這步同2。剩下的便是一個個與上一個問題一樣的子問題了。

 字典序:

  1. S=0 且 m<= s[i]=0的個數:此時只要在s[i]=0的所有i中跑單調隊列即可。即維護隊列里面a[i]從小到大,然后按照輸入順序入隊,只要后面s[i]=0的個數還足夠,就一直入隊。
  2. 其它情況:這個比較麻煩。假設我們上一次選了a[last],則下次的i需滿足abs(s[last+1]-s[i])<=ans 且 s[i+1]/m'上取整<=ans(m'為剩下的天數) 且 n-i>=m,就相當於一個子問題。於是,可以按s[i]分類,為每個s[i]開一個隊列,維護隊列a[i]單調遞増,只要后面的個數夠,就不斷入隊。選的時候從s[last]-ans到s[last]+ans枚舉,先把下標<last的彈出,然后選出a[i]最小的一個。詳見代碼。
/**************************************************************
    Problem: 3141
    User: lazycal
    Language: C++
    Result: Accepted
    Time:2696 ms
    Memory:41820 kb
****************************************************************/
 
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <queue>
#define _(x) static_cast<double>(x)
#define __(x) static_cast<int>(x)
const int N=500000+9;
int n,m,sum[N],cnt[N],a[N],tot;
struct node{int l,r,x;}t[N*2];
int newnode(const int l,const int r,const int x)
{
    t[++tot].x=x; t[tot].l=l; t[tot].r=r;
    return tot;
}
class Queue
{
    int be,en,len;
public:
    void push_back(int x)
    {
        if (!len) be=en=newnode(0,0,x);
        else t[en].r=newnode(en,0,x),en=t[en].r;
        ++len;
    }
    bool empty(){return !len;}
    int front(){return t[be].x;}
    int back(){return t[en].x;}
    void pop_back()
    {
        en=t[en].l;
        --len;
    }
    void pop_front()
    {
        be=t[be].r;
        --len;
    }
    void push(int x)
    {
        while (!empty() && a[back()]>a[x]) pop_back();
        push_back(x);
    }
}Qu[N*2],*qu=Qu+N,Q[N*2],*q=Q+N;
int Min(const int x,const int y)
{
    if (a[x]<a[y]) return x;
    return y;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("3141.in","r",stdin);
    freopen("3141.out","w",stdout);
    #endif
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;++i) {
        scanf("%d%d",a+i,sum+i);
        if (!sum[i]) sum[i] = -1;
    }
    for (int i=n-1;i;--i) sum[i] += sum[i+1];
    for (int i=n;i;cnt[i] += cnt[i+1], --i)
        if (!sum[i]) ++cnt[i];
    int S = sum[1], d = S ? (abs(S) - 1)/m + 1 : cnt[1] < m;
    cnt[n+1]=-1;
    if (!d) {
        for (int i=1,j=2; i<m; ++i) {
            for (; cnt[j+1]>=m-i; ++j)
                if (!sum[j+1]) q[0].push(j);
            printf("%d ",a[q[0].front()]);
            q[0].pop_front();
        }
    }else {
        a[n+1]=n+1; int la = 0;
        for (int i=2; i<=n; ++i)
            qu[sum[i]].push_back(i-1);
        for (int i=1; i<m; ++i) {
            int ans = n+1;
            for (int j=sum[la+1]-d; j<=sum[la+1]+d; ++j) {
                if (__(ceil(_(abs(j))/(m-i))) > d) continue;
                for (; !qu[j].empty() && n-qu[j].front() >= m-i; qu[j].pop_front())
                    /*printf("test: %d\n",qu[j].front()),*/if (qu[j].front() > la) q[j].push(qu[j].front());
                for (; ! q[j].empty() &&    q[j].front() <= la;   q[j].pop_front());
                if (!q[j].empty())
                    ans = Min(ans,q[j].front());
            }
            la = ans;
            printf("%d ",a[ans]);
        }
    }
    printf("%d",a[n]);
}

  

[bzoj3142][Hnoi2013]數列

這題也不會……實在是太弱了

令a[i]為相鄰兩天股票價格之差,可以發現對於一個a序列,對ans的貢獻是N-a[1]-a[2]-...-a[K-1]。於是答案就是N*M^(K-1)-M*(M+1)/2*M^(K-2)*(K-1)

 

/**************************************************************
    Problem: 3142
    User: lazycal
    Language: C++
    Result: Accepted
    Time:0 ms
    Memory:804 kb
****************************************************************/
 
#include <cstdio>
long long P;
long long power(long long x,int k)
{
    long long res=1;
    for (;k;x=x*x%P,k/=2)
        if (k&1) res=res*x%P;
    return res;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("3142.in","r",stdin);
    freopen("3142.out","w",stdout);
    #endif
    //N*M^(K-1)-M*(M+1)/2*M^(K-2)*(K-1)
    long long N,K,M;
    scanf("%lld%lld%lld%lld",&N,&K,&M,&P);
    if (K==1) {printf("%lld\n",N%P);return 0;}
    long long tmp=power(M,K-2),ans=(N%P*tmp%P*M%P-M*(M+1)/2%P*tmp%P*(K-1)%P)%P;
    printf("%lld\n",(ans+P)%P);
}

  

[bzoj3143][Hnoi2013]游走

這題……不講了……高斯消元……

/**************************************************************
    Problem: 3143
    User: lazycal
    Language: C++
    Result: Accepted
    Time:1008 ms
    Memory:6880 kb
****************************************************************/
 
#include <cstdio>
#include <cmath>
#include <algorithm>
const int N=500+9,M=N*N;
const double EPS = 1e-9;
struct edge{int u,v;}es[M];
double times[M],a[N][N];
int d[N],n,m;
void Guass(int equ,int val)
{
    for (int col = 1, row = 1; col <= val; ++col, ++row) {
        int maxr = row;
        for (int i = row + 1; i <= equ; ++i)
            if (fabs(a[i][col]) > fabs(a[maxr][col])) maxr = i;
        if (maxr != row) for (int i = col; i <= val + 1; ++i) std::swap(a[maxr][i],a[row][i]);
        for (int i = col + 1; i <= val + 1; ++i) a[row][i] /= a[row][col];
        a[row][col] = 1;
        for (int i = 1; i <= equ; ++i)
            if (i != row) {
                for (int j = col + 1; j <= val + 1; ++j)
                    a[i][j] -= a[i][col]*a[row][j];
                a[i][col]=0;
            }
    }
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("3143.in","r",stdin);
    freopen("3143.out","w",stdout);
    #endif
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;++i) {
        scanf("%d%d",&es[i].u,&es[i].v);
        ++d[es[i].u]; ++d[es[i].v];
    }
    for (int i=1;i<=m;++i) {
        a[es[i].u][es[i].v]=-static_cast<double>(1)/d[es[i].v];
        a[es[i].v][es[i].u]=-static_cast<double>(1)/d[es[i].u];
    }
    for (int i=1;i<n;++i)
        a[i][i]=1,a[i][n]=0;
    a[n][n]=0;
    a[1][n]=1;
    Guass(n-1,n-1);
    for (int i=1;i<=m;++i) 
        times[i]=a[es[i].u][n]/d[es[i].u]+a[es[i].v][n]/d[es[i].v];
    std::sort(times+1,times+1+m);
    double ans=.0;
    for (int i=1;i<=m;++i) ans+=times[i]*(m-i+1);
    printf("%.3f\n",ans);
}

  

[bzoj3144][Hnoi2013]切糕

網絡流最小割。增加一層,然后每層向上層連一條邊,容量為下層點權。對於第k層的點,每個點都向k-d層連一條容量為無窮大的邊。源向底層每個點連容量為無窮大的邊,頂層向匯連容量為無窮大的邊。然后跑一遍最小割。

/**************************************************************
    Problem: 3144
    User: lazycal
    Language: C++
    Result: Accepted
    Time:964 ms
    Memory:9260 kb
****************************************************************/
 
#include <cstdio>
#include <cstring>
#include <algorithm>
const int N=65602,INF=0x7fffffff;
int src,sink,Vc,cnt[N],dis[N],ec,p,q,r,d,son[N];
struct Edge
{
    int next,v,f;
}es[N*10];
inline int node(const int x,const int y,const int z){return x*p*q+y*q+z;}
inline void addedge(const int x,const int y,const int z)
{
    es[ec].v=y; es[ec].f=z;
    es[ec].next=son[x]; son[x]=ec++;
}
inline void Addedge(const int x,const int y,const int z){addedge(x,y,z);addedge(y,x,0);}
int sap(const int u,const int aug)
{
    if (u==sink) return aug;
    int sum=0,mindis=Vc;
    for (int i=son[u];i!=-1;i=es[i].next) {
        const int v=es[i].v;
        if (es[i].f && dis[v]+1==dis[u]) {
            int t=sap(v,std::min(aug-sum,es[i].f));
            sum+=t; es[i].f-=t; es[i^1].f+=t;
            if (sum==aug || dis[src]>=Vc) return sum;
        }
        if (es[i].f && mindis>dis[v]) mindis=dis[v];
    }
    if (!sum) {
        if (!--cnt[dis[u]]) dis[src]=Vc;
        ++cnt[dis[u]=mindis+1];
    }
    return sum;
}
int max_flow()
{
    cnt[0]=Vc;
    int flow=0;
    while (dis[src]<Vc) flow+=sap(src,INF);
    return flow;
}
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("3144.in","r",stdin);
    freopen("3144.out","w",stdout);
    #endif
    scanf("%d%d%d%d",&p,&q,&r,&d);
    memset(son,-1,sizeof son);
    for (int i=0;i<r;++i)
        for (int j=0;j<p;++j)
            for (int k=0,x;k<q;++k) {
                scanf("%d",&x);
                Addedge(node(i,j,k),node(i+1,j,k),x);
                if (i-d>=0) {
                    if (j-1>=0) Addedge(node(i,j,k),node(i-d,j-1,k),INF);
                    if (j+1<p)  Addedge(node(i,j,k),node(i-d,j+1,k),INF);
                    if (k-1>=0) Addedge(node(i,j,k),node(i-d,j,k-1),INF);
                    if (k+1<q)  Addedge(node(i,j,k),node(i-d,j,k+1),INF);
                }
            }
    Vc=(r+1)*p*q+2,src=(r+1)*p*q,sink=(r+1)*p*q+1;
    for (int i=0;i<p;++i)
        for (int j=0;j<q;++j)
            Addedge(src,node(0,i,j),INF),Addedge(node(r,i,j),sink,INF);
    printf("%d\n",max_flow());
}

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM