BZOJ 4002~4008 总结

BZOJ 4002 有意义的字符串

题目:

给定b,d,nb,d,nb,d,n,求⌊(b+d2)n⌋mod  7528443412579576937\lfloor(\frac{b+\sqrt{d}}{2})^n\rfloor\mod 7528443412579576937(2b+d)nmod7528443412579576937

其中,0&lt;b2≤d≤(b+1)2≤1018,n≤10180&lt;b^2\leq d\leq(b+1)^2\leq 10^{18},n\leq 10^{18}0<b2d(b+1)21018,n1018,并且bmod&ThinSpace;&ThinSpace;2=1,dmod&ThinSpace;&ThinSpace;4=1b\mod 2=1,d\mod 4 =1bmod2=1,dmod4=1

思路:

求这样一个东西的nnn次方然后向下取整,再取模,这很显然是不能够直接快速幂的。所以对下取整里面的东西进行考虑。将(b+d2)n(\frac{b+\sqrt{d}}{2})^n(2b+d)n二项式展开,会发现只有d\sqrt dd的奇数次项是小数,其余都是整数。因此可以通过加上(b−d2)n(\frac{b-\sqrt{d}}{2})^n(2bd)n来消去d\sqrt dd的奇数次项。

所以我们将所求转化为⌊[(b+d2)n+(b−d2)n]−(b−d2)n⌋\lfloor [(\frac{b+\sqrt d}{2})^n+ (\frac{b-\sqrt d}{2})^n]- (\frac{b-\sqrt d}{2})^n \rfloor[(2b+d)n+(2bd)n](2bd)n

对于前半部分,这样的形式正好对应了二阶线性递推的通项的形式。

x1=b+d2,x2=b−d2x_1=\frac{b+\sqrt d}{2},x_2=\frac{b-\sqrt d}{2}x1=2b+d,x2=2bd ,则原式前半部分变成x1n+x2nx_1^n+x_2^nx1n+x2n

f(n)=x1n+x2nf(n)=x_1^n+x_2^nf(n)=x1n+x2n,则该函数的二阶线性递推式为

f(n)−(x1+x2)f(n−1)+(x1x2)f(n−2)=0f(n)-(x_1+x_2)f(n-1)+(x_1x_2)f(n-2)=0f(n)(x1+x2)f(n1)+(x1x2)f(n2)=0

化简得

f(n)=(x1+x2)f(n−1)−(x1x2)f(n−2)f(n)=(x_1+x_2)f(n-1)-(x_1x_2)f(n-2)f(n)=(x1+x2)f(n1)(x1x2)f(n2)

f(n)=bf(n−1)+d−b24f(n−2)f(n)=bf(n-1)+\frac{d-b^2}{4}f(n-2)f(n)=bf(n1)+4db2f(n2)

对于后半部分,可以发现⌊d⌋=b\lfloor\sqrt d\rfloor=bd=b,所以0≤∣(b−d)n∣&lt;10\leq|(b-\sqrt d)^n|&lt;10(bd)n<1

关于正负性只需要看nnn的奇偶性即可。

因此就能够巧妙地去掉了下取整的影响。

代码:

/**************************************************************
    Problem: 4002
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:48 ms
    Memory:1292 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std;
 
typedef unsigned long long ll;
const ll p=7528443412579576937ull;
ll b,d,n;
 
ll read()
{
    ll x=0;char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return x;
}
 
inline ll qmul(ll y,ll z)
{
    ll x=0;
    for (;y;y>>=1,z=z+z,z=(z>=p)?z-p:z)
        if (y&1) x=x+z,x=(x>=p)?x-p:x;
    return x;
}
 
inline ll add(ll x,ll y)
{
    x+=y;x=(x>p)?x-p:x;
    return x;
}
 
int matsize=2;
struct Tmat
{
    ll v[2][2];
    void reset(bool op)
    {
        memset(v,0,sizeof v);
        if (op) for (int i=0;i<matsize;i++) v[i][i]=1;
    }
    Tmat operator * (const Tmat &c) const
    {
        Tmat d;d.reset(0);
        for (int i=0;i<matsize;i++)
            for (int k=0;k<matsize;k++)
                for (int j=0;j<matsize;j++)
                    d.v[i][j]=add(d.v[i][j],qmul(v[i][k],c.v[k][j]));
        return d;
    }
}A,B;
 
void init()
{
    b=read(),d=read(),n=read();
    if (n==0) {puts("1");exit(0);}
    B.reset(0),A.reset(0);
    B.v[0][0]=b,B.v[0][1]=1,B.v[1][0]=(d-qmul(b,b))/4,B.v[1][1]=0;
    A.v[0][0]=b,A.v[0][1]=2;
}
 
Tmat &qpow(Tmat x,ll y)
{
    static Tmat ret;ret.reset(1);
    for (;y;y>>=1,x=x*x) if(y&1)ret=ret*x;
    return ret;
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4002.in","r",stdin);
    freopen("4002.out","w",stdout);
    #endif
    init();
    A=A*qpow(B,n-1);
    printf("%lld\n",A.v[0][0]-((n&1)^1)*(b*b!=d));
    return 0;
}

BZOJ 4003 城池攻占

题目:

nnn座城池,每座城池有一个管辖城池fif_ifi,防御值hih_ihi和攻占后的提升值ai,via_i,v_iai,vi。有mmm个骑士,每个骑士有一个战斗力sis_isi和一个初始目标城池cic_ici。若骑士战斗力低于城池防御值,则骑士牺牲于改城池,否则骑士攻占成功。若骑士成功攻占城池iii,则获得战斗力提升。若参数ai=0a_i=0ai=0,则骑士战斗力sis_isi增加viv_ivi,若参数ai=1a_i=1ai=1,则骑士的战斗力乘上viv_ivi。骑士成功攻占城池iii,后会选择继续攻占城池fif_ifi,直到攻占完111号城池。

思路:

维护一个数据结构支持快速插入,和删除最小值。左偏树显然可以,但这里写了启发式合并的大根堆(stl)。有一些细节需要注意,不能暴力修改骑士的战斗力值,因此需要在城池iii所在的堆上打标记,出入堆顶都要buff一下。关于启发式合并,直接使用一个指针数组表示城池iii的堆的编号是多少。

代码:

/**************************************************************
    Problem: 4003
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:5448 ms
    Memory:114388 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
typedef long double ldb;
typedef long long ll;
const int N=3e5+10;
 
struct Tcity
{
    ldb h,v;
    int fa,a,ocp,id;
}city[N];
 
struct Tknight
{
    ldb s;
    int c,ocp,id;
    bool operator < (const Tknight &i) const {return s>i.s;}
}knight[N];
 
struct Tedge
{
    int v,nxt;
}e[N*2];
int head[N],cnt;
 
struct Tbuff
{
    ldb mul,add;
    int ocp;
    ldb buff(ldb x){return x*mul+add;}      // when get out
    ldb debuff(ldb x){return (x-add)/mul;}  // when get in
}buf[N];
 
priority_queue<Tknight>stay[N];
int n,m,pos[N];
 
ll read()
{
    ll x=0,f=1;char c=getchar();
    for (;!isdigit(c);c=getchar())f&=c-'-';
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return f?x:-x;
}
 
void write(int x)
{
    if (x>9) write(x/10);
    putchar(x%10+48);
}
 
void addedge(int x,int y)
{
    e[++cnt]=Tedge{y,head[x]};
    head[x]=cnt;
}
 
void init()
{
    n=read(),m=read();  
    for (int i=1;i<=n;i++) city[i].h=read(); 
    for (int i=2;i<=n;i++) city[i].fa=read(),city[i].a=read(),city[i].v=read(),city[i].id=i; city[1].id=1;
    for (int i=1;i<=m;i++) knight[i].s=read(),knight[i].c=read(),knight[i].ocp=0,knight[i].id=i,stay[knight[i].c].push(knight[i]);
    for (int i=1;i<=n;i++) addedge(city[i].fa,i),addedge(i,city[i].fa);
    for (int i=1;i<=n;i++) pos[i]=i,buf[i].add=0,buf[i].mul=1,buf[i].ocp=0;
}
 
void dfs(int x,int y)
{
    for (;!stay[pos[x]].empty();)   //  pop -- check the knight in the xth city
    {
        Tknight it=stay[pos[x]].top();stay[pos[x]].pop();
        if (buf[pos[x]].buff(it.s)>=city[x].h)   {stay[pos[x]].push(it);break;}   
            else                                {it.ocp+=buf[pos[x]].ocp;knight[it.id].ocp=it.ocp;city[x].ocp++;} // knight die in the xth city
    }
    for (int i=head[x];i;i=e[i].nxt)
        if (e[i].v!=y)
        {
            dfs(e[i].v,x);
            int son=pos[e[i].v];
            for (;!stay[son].empty();)  //pop -- check the knight in the son's city
            {
                Tknight it=stay[son].top();stay[son].pop();
                if (buf[son].buff(it.s)>=city[x].h)  {stay[son].push(it);break;}
                    else                            {it.ocp+=buf[son].ocp;knight[it.id].ocp=it.ocp;city[x].ocp++;}
            }
            if (stay[son].size()>stay[pos[x]].size()) swap(pos[x],son);
            for (;!stay[son].empty();)      //merge
            {
                Tknight it=stay[son].top();stay[son].pop();
                it.ocp+=buf[son].ocp-buf[pos[x]].ocp;
                it.s=buf[pos[x]].debuff(buf[son].buff(it.s));
                stay[pos[x]].push(it);
            }
        }
    // buff the queue
    if (city[x].a)  buf[pos[x]].add*=city[x].v,buf[pos[x]].mul*=city[x].v;
        else        buf[pos[x]].add+=city[x].v;
    buf[pos[x]].ocp++;
    // the end
    if (x==1) for (;!stay[pos[x]].empty();) 
    {
        Tknight it=stay[pos[x]].top();stay[pos[x]].pop();
        it.ocp+=buf[pos[x]].ocp;knight[it.id].ocp=it.ocp;
    }
}
 
void solve()
{
    dfs(1,0);
    for (int i=1;i<=n;i++) write(city[i].ocp),putchar('\n');
    for (int i=1;i<=m;i++) write(knight[i].ocp),putchar('\n');
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4003.in","r",stdin);
    freopen("4003.out","w",stdout);
    #endif
     
    init();
    solve();
     
    return 0;
}

BZOJ 4004 装备购买

题目:

nnnmmm维向量z⃗(a0,......,am)\vec{z}(a_0,......,a_m)z(a0,......,am),每个向量有一个权值cic_ici,若一个向量能够用已选择向量表示为∑λizi⃗\sum{\lambda_i\vec{z_i}}λizi,则该向量不能够被选择。问最多能选多少个向量,以及在此情况下的最小权值是多少。

思路:

显然是一个线性基。关于线性基怎么维护,只要每次插入一个新的基底的时候将这个基底的前面已存在基底的位置消成零。最后排个序从小到大插入基底即可。

代码:

/**************************************************************
    Problem: 4004
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:3660 ms
    Memory:7368 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
typedef long double db;
const int N=505;
const db eps=1e-8;
int n,m,ans,tot;
db base[N][N];
 
struct Tvector
{
    db vec[N]; int cost;
    bool operator < (const Tvector &i) const {return cost<i.cost;}
}f[N];
 
int read()
{
    int x=0;char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return x;
}
 
void init()
{
    n=read(),m=read();
    for (int i=1;i<=n;i++) for (int j=1;j<=m;j++) f[i].vec[j]=read();
    for (int i=1;i<=n;i++) f[i].cost=read();
}
 
void solve()
{
    sort(f+1,f+1+n);
    memset(base,0,sizeof base);
    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++)
        {
            if (fabs(base[j][j])<=eps&&fabs(f[i].vec[j])>eps) 
            {
                for (int k=j;k<=m;k++)
                    base[j][k]=f[i].vec[k];
                ans+=f[i].cost,tot++;
                break;
            }
            db a=f[i].vec[j]/base[j][j];
            for (int k=j;k<=m;k++)
                f[i].vec[k]-=a*base[j][k];
        }
         
    }
    printf("%d %d\n",tot,ans);
}
 
int main()
{
     
    #ifndef ONLINE_JUDGE
    freopen("4004.in","r",stdin);
    freopen("4004.out","w",stdout);
    #endif
     
    init();
    solve();
     
    return 0;
}

BZOJ 4005 骗我呢

题目:

有一个n×mn\times mn×m的数组xi,j(1≤i≤n;1≤j≤m)x_{i,j}(1\leq i\leq n;1\le j \le m)xi,j(1in;1jm)

对于∀xi,j∈[0,m]\forall x_{i,j} \in [0,m]xi,j[0,m]

对于1≤i≤n;1≤j&lt;m1\leq i\le n;1\leq j&lt;m1in;1j<ms.t.xi,j&lt;xi,j+1s.t. x_{i,j}&lt;x_{i,j+1}s.t.xi,j<xi,j+1

对于1&lt;i≤n;1≤j&lt;m1&lt;i\leq n;1\leq j &lt;m1<in;1j<ms.t.xi,j&lt;xi−1,j+1s.t. x_{i,j}&lt;x_{i-1,j+1}s.t.xi,j<xi1,j+1

xi,jx_{i,j}xi,j的方案数对109+710^9+7109+7取模的结果。

思路:

由题目可知,对于每一行xi,jx_{i,j}xi,j,有且仅有一个分界点kkk使得xi,j=j−1(j≤k),xi,j=j(j&gt;k)x_{i,j}=j-1(j\leq k),x_{i,j}=j(j&gt;k)xi,j=j1(jk),xi,j=j(j>k)

若第iii行的分界点为kkk,则第i+1i+1i+1行的分界点一定大于等于k−1k-1k1

f(i,j)f(i,j)f(i,j)表示第iii行的分界点在jjj的方案数

显然有$f(i,j)=\sum_{k=0}^{k\leq j+1}{f(i-1,k)} $

化简得f(i,j)=f(i,j−1)+f(i−1,j+1)f(i,j)=f(i,j-1)+f(i-1,j+1)f(i,j)=f(i,j1)+f(i1,j+1)

转移如下图:

新建一些空节点使得转移变成如下图:

通过一一对应可以得到,上图中从(0,0)(0,0)(0,0)(n+m+1,n)(n+m+1,n)(n+m+1,n)且不过直线y=x+1y=x+1y=x+1和直线y=x−m−2y=x-m-2y=xm2的路径数就是答案。要计算从S1(0,0)S_1(0,0)S1(0,0)往上往右走到T(n+m,n−1)T(n+m,n-1)T(n+m,n1)的且不过直线l1:y=x+1l_1:y=x+1l1:y=x+1和直线l2:y=x−m−2l_2:y=x-m-2l2:y=xm2方案数,可以通过容斥计算。
S1S_1S1TTT的路径数减去S1S_1S1关于l1l_1l1对称的对称点S2S_2S2TTT的路径。从S2S_2S2TTT的路径一定会经过直线l1l_1l1,但不保证不经过l2l_2l2,所以还要再减去作S1S_1S1关于l2l_2l2对称的对称点S3S_3S3TTT的路径数。但是这又会重复减去了既经过l1l_1l1,又经过l2l_2l2的部分。因此还要再加上S2S_2S2关于l2l_2l2对称的对称点到TTT的路径数,和S3S_3S3关于l1l_1l1对称的对称点到TTT的路径数。以此类推下去,直到对称点不在TTT的左下方时停止。

代码:

/**************************************************************
    Problem: 4005
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:7300 ms
    Memory:63792 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
typedef long long ll;
const ll mod=1000000007;
const ll INV=647658926; // inv(maxn!)
const int maxn=4e6;
const int N=4e6+10; 
ll fac[N],inv[N];
int n,m,ans;
 
int read()
{
    int x=0;char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return x;
}
 
ll qpow(ll x,ll y)
{
    ll ret=1;
    for (;y;y>>=1,x=x*x%mod)
        if (y&1) ret=ret*x%mod;
    return ret;
}
 
void init()
{
    inv[0]=fac[0]=1; inv[maxn]=INV;
    for (int i=1;i<=maxn;i++)    fac[i]=fac[i-1]*i%mod; //cerr<<qpow(fac[maxn],mod-2)<<endl;
    for (int i=maxn-1;i>=1;i--)  inv[i]=inv[i+1]*(i+1)%mod;
    n=read(),m=read();
}
 
// target: (1,1)-->(n+m+2,n+1) s.t. path didn't cross "y=x+1" and "y=x-m-2" 
 
int calc(int x,int y)
{
    int p=n+m+2-x,q=n+1-y;
    if (p<0||q<0) return 0;
    // val=(p+q)!/(p!q!)
    return 1ll*fac[p+q]*inv[p]%mod*inv[q]%mod;
}
 
void solve()
{
    int sx=1,sy=1;
    for (int op=1;;op*=-1)
    {
        int val=calc(sx,sy); 
        if (!val) break;
        ans+=val*op;
        if (ans<0) ans+=mod;
        if (ans>mod) ans-=mod;
        if (op>0)    {int cx=sy-1,d=sx-cx; sy+=d,sx-=d;} // A symtric
            else    {int cx=sy+m+2,d=sx-cx;sy+=d,sx-=d;}// B symtric
    }
    sx=m+3,sy=-1-m; // (1,1) (B symtric)
    for (int op=-1;;op*=-1)
    {
        int val=calc(sx,sy); 
        if (!val) break;
        ans+=val*op;
        if (ans<0) ans+=mod;
        if (ans>mod) ans-=mod;
        if (op<0)    {int cx=sy-1,d=sx-cx; sy+=d,sx-=d;} // A symtric
            else    {int cx=sy+m+2,d=sx-cx;sy+=d,sx-=d;}// B symtric
    }
    printf("%d\n",ans);
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4005.in","r",stdin);
    freopen("4005.out","w",stdout);
    #endif
    init();
    solve();
    return 0;
}

BZOJ 4006 管道连接

题目

该部门有nnn个情报站,用111nnn的整数编号。给出mmm对情报站ui;viu_i;v_iui;vi和费用wiw_iwi,表示情报站uiu_iuiviv_ivi之间可以花费wiw_iwi单位资源建立通道。如果一个情报站经过若干个建立好的通道可以到达另外一个情报站,那么这两个情报站就建立了通道连接。形式化地,若uiu_iuiviv_ivi建立了通道,那么它们建立了通道连接;若uiu_iuiviv_ivi均与tit_iti建立了通道连接,那么uiu_iuiviv_ivi也建立了通道连接。现在在所有的情报站中,有ppp个重要情报站,其中每个情报站有一个特定的频道。问题是求需要花费最少的资源,使得任意相同频道的情报站之间都建立通道连接。

对于所有数据,0&lt;ci≤p≤10;0&lt;ui,vi,di≤n≤1000;0≤m≤3000;0≤wi≤200000&lt;c_i\le p\le 10;0&lt;u_i,v_i,d_i\le n \le 1000;0\le m\le 3000;0\le w_i\le 200000<cip10;0<ui,vi,din1000;0m3000;0wi20000

思路:

只有101010个关键点,因此可以考虑设一个2102^{10}210的状态来存储这些关键点的连通性。这可以用O(n2p)O(n2^p)O(n2p)的时间解决,具体实现是裸的斯坦纳树。但题目要求的是频道相同的特殊点才要求一定连通,所以可以考虑二次dp。首先,在第一次dp完之后,相同频道不连通的状态是一定不合法的,可以不用考虑。将所有合法状态取出来,做一个背包就能拼出所有同频道特殊点连通的最小花费。

代码:

/**************************************************************
    Problem: 4006
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:9568 ms
    Memory:18576 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
const int P=20,M=3e3+100,N=1e3+100,S=4000,inf=0x3f3f3f3f;
 
struct Tstation
{
    int c,d;
    bool operator < (const Tstation &i) const{return d<i.d;}
}station[P];
 
struct Tedge
{
    int v,w,nxt;
}e[M*2];
 
int n,m,p,cnt,head[N],f[N][S],g[S],sum[P],tmp[P];
bool vis[N],check[S];
queue<int>q;
 
int read()
{
    int x=0;char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return x;
}
 
void addedge(int x,int y,int z)
{
    e[++cnt]=Tedge{y,z,head[x]};
    head[x]=cnt;
}
 
void init()
{
    n=read(),m=read(),p=read();
    for (int i=1;i<=m;i++)
    {
        int u=read(),v=read(),w=read();
        addedge(u,v,w),addedge(v,u,w);
    }
    for  (int i=1;i<=p;i++)
    {
        station[i].c=read();
        station[i].d=read();
    }
}
 
void spfa(int s)
{
    for (int i=1;i<=n;i++)
        if (f[i][s]<inf) q.push(i),vis[i]=1;
    for (;!q.empty();q.pop())
    {
        int u=q.front();vis[u]=0;
        for (int i=head[u];i;i=e[i].nxt)
            if (f[e[i].v][s]>f[u][s]+e[i].w)
            {
                f[e[i].v][s]=f[u][s]+e[i].w;
                if (!vis[e[i].v]) q.push(e[i].v),vis[e[i].v]=1;
            }
    }
}
 
void solve()
{
    memset(f,0x3f,sizeof f);
    memset(g,0x3f,sizeof g);
    sort(station+1,station+p);
    for (int i=1;i<=p;i++) 
        f[station[i].d][1<<i-1]=0;
    for (int i=1;i<=p;i++)
        sum[station[i].c]++;
    for (int i=1;i<=n;i++) f[i][0]=0;
    for (int i=1;i<(1<<p);i++)
    {
        for (int j=1;j<=n;j++)
            for (int k=i;k;k=i&(k-1))
                f[j][i]=min(f[j][k]+f[j][i^k],f[j][i]);
        spfa(i);
    }
    for (int i=1;i<(1<<p);i++)
    {
        check[i]=1;
        for (int j=0;j<p;j++)
            if (i>>j&1) tmp[station[j+1].c]++;
        for (int j=0;j<p;j++)
            if (i>>j&1) if (tmp[station[j+1].c]!=sum[station[j+1].c]) check[i]=0;
        for (int j=0;j<p;j++)
            tmp[j+1]=0;
    }
    g[0]=0;
    for (int i=1;i<(1<<p);i++) if (check[i])
    {
        for (int j=1;j<=n;j++) 
            g[i]=min(g[i],f[j][i]);
        for (int j=1;j<=n;j++)
            for (int k=i;k;k=i&(k-1))   if (check[k])
                g[i]=min(g[i],g[k]+g[i^k]);
    }
    printf("%d\n",g[(1<<p)-1]);
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4006.in","r",stdin);
    freopen("4006.out","w",stdout);
    #endif
     
    init();
    solve();
     
    return 0;
}

BZOJ 4007 战争调度

题目:

王国里的公民每个公民有两个下属或者没有下属,这种关系刚好组成一个nnn层的完全二叉树。公民iii的下属是2i2i2i2i+12i+12i+1。最下层的公民即叶子节点的公民是平民,平民没有下属,最上层的是国王,中间是各级贵族。现在这个王国爆发了战争,国王需要决定每一个平民是去种地以供应粮食还是参加战争,每一个贵族(包括国王自己)是去管理后勤还是领兵打仗。一个平民会对他的所有直系上司有贡献度,若一个平民iii参加战争,他的某个直系上司jjj领兵打仗,那么这个平民对上司的作战贡献度为wi,jw_{i,j}wi,j。若一个平民iii种地,他的某个直系上司jjj管理后勤,那么这个平民对上司的后勤贡献度为fi,jf_{i,j}fi,j,若 iiijjj所参加的事务不同,则没有贡献度。为了战争需要保障后勤,国王还要求不多于mmm个平民参加战争。整个王国所有贵族得到的贡献度最大的最大值。

对于所有数据,2≤n≤10;m≤2n−1;0≤wi,j,fi,j≤20002\le n\le 10;m\le 2^{n}-1;0\leq w_{i,j},f_{i,j}\le 20002n10;m2n1;0wi,j,fi,j2000

思路:

对于每个平民,他能影响的贵族只有nnn个,考虑用2n2^n2n枚举这nnn个贵族的状态,可以得到每个平民对父链所有状态的影响。考虑如何进行贡献,首先,平民对所有贵族的贡献已经计算了,所以考虑某个贵族的状态的时候不需要再根据平民的状态来计算自身的贡献。在父链相同的情况下,取贵族的的两种贡献的最大值作为当前为根的子树的最大值。向上贡献就直接将最大值加入父亲的dp值里即可。

代码:

/**************************************************************
    Problem: 4007
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:168 ms
    Memory:5520 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
const int N=11,M=1030;
int n,m,ans,f[M][N],w[M][N],dp[M][M];
bool career[N];
 
int read()
{
    int x=0;char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (; isdigit(c);c=getchar())x=x*10+(c^48);
    return x;
}
 
void init()
{
    n=read(),m=read();
    for (int i=1;i<=(1<<n-1);i++)
        for (int j=1;j<=n-1;j++)
            w[(1<<n-1)-1+i][j]=read();    // fight
    for (int i=1;i<=(1<<n-1);i++)
        for (int j=1;j<=n-1;j++)
            f[(1<<n-1)-1+i][j]=read();    // farm
}
 
void dfs(int x,int layer)
{
    for (int i=0;i<=1<<layer;i++) dp[x][i]=0;
    if (layer==0)
    {
        for (int i=1;i<=n-1;i++)
            if (career[i])  dp[x][1]+=w[x][i];
                else        dp[x][0]+=f[x][i];
        return ;
    }
    career[layer]=1;
    dfs(x<<1,layer-1),dfs(x<<1|1,layer-1);
    for (int i=0;i<=1<<layer-1;i++)
        for (int j=0;j<=1<<layer-1;j++)
            dp[x][i+j]=max(dp[x][i+j],dp[x<<1][i]+dp[x<<1|1][j]);
    career[layer]=0;
    dfs(x<<1,layer-1),dfs(x<<1|1,layer-1);
    for (int i=0;i<=1<<layer-1;i++)
        for (int j=0;j<=1<<layer-1;j++)
            dp[x][i+j]=max(dp[x][i+j],dp[x<<1][i]+dp[x<<1|1][j]);
}
 
void solve()
{
    dfs(1,n-1);
    for (int i=0;i<=m;i++)
        ans=max(ans,dp[1][i]);
    printf("%d\n",ans);
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4007.in","r",stdin);
    freopen("4007.out","w",stdout);
    #endif
     
    init();
    solve();
     
    return 0;
}

BZOJ 4008 亚瑟王

题目:

nnn张卡牌排列成某种顺序,依次编号为111nnn,顺序即为输入。第iii张卡牌的发动概率为pip_ipi,如果发动,会对敌方造成did_idi点伤害,0&lt;pi&lt;10&lt;p_i&lt;10<pi<1

共有rrr轮游戏,每轮中:
如果这张卡牌在这一局游戏中已经发动过技能,则丢弃这张卡牌;否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第iii张,则将其以 pi的

概率发动技能:如果技能发动,则对敌方造成did_idi点伤害,并结束这一轮,否则继续考虑下一张卡牌,如果这张卡牌已经是最后一张,则结束这一轮。

错误思路:

首先直观感觉考虑第iii张卡牌在第jjj轮中发动的概率,发现这样考虑会受到前面的卡牌是否被选择的影响,并且这种影响不具有一般性。头铁继续考虑下去

,将轮数倒过来考虑,会神奇的发现——每次的选择会影响之前的概率。假如在这一次选择了第iii张卡牌,则第j(i&lt;j≤n)j(i&lt;j\le n)j(i<jn)张卡牌选择的概率(期望)会

乘上(1−pi)(1-p_i)(1pi),再结合选择后面以及选择它自己对它的影响,就能递推求出第rrr轮的期望和了吧…吧。
其实,这是错误的。因为第iii张卡牌对第jjj张卡牌的影响的概率应该是累加的,而不是累乘的。这样计算会导致第iii张卡牌不出现的概率多次影响第jjj

卡牌。至于为什么要分享这一心路历程,个人认为也是一种不错的收获。

思路:

直接考虑第iii张卡牌在轮次中的影响不可做,那么有一种比较套路的想法就是将影响一般化,使得这些影响可以用状态的增减来简单表示。直接考虑第

iii张卡牌在rrr轮中被选中的概率。第iii张卡牌在rrr轮选择中都没有被选到的概率为(1−pi)r(1-p_i)^r(1pi)r,当然这个概率受到前面卡牌的影响,但是这样的概率被

一般化了。引入状态jjj,用f(i,j)f(i,j)f(i,j)表示在rrr轮中,前iii张卡牌被选了jjj张的概率。如何进行转移呢?考虑第i+1i+1i+1张卡牌在f(i,j)f(i,j)f(i,j)状态下被选择的概

P=f(i,j)∗(1−(1−pi+1)r−j)P=f(i,j)*(1-(1-p_{i+1})^{r-j})P=f(i,j)(1(1pi+1)rj),因此f(i,j)→f(i+1,j+1)f(i,j)\to f(i+1,j+1)f(i,j)f(i+1,j+1)。不被选择的概率也显然,所以f(i,j)→f(i,j+1)f(i,j)\to f(i,j+1)f(i,j)f(i,j+1)。求出fff后,再求每张卡牌在

rrr轮中被选中的概率就好求了。Pi=∑j=0min(i−1,r)(1−(1−pi)r−j)×f(i−1,j)P_i=\sum_{j=0}^{min(i-1,r)} (1-(1-p_i)^{r-j})\times f(i-1,j)Pi=j=0min(i1,r)(1(1pi)rj)×f(i1,j),所以期望伤害就是∑Pi×di\sum P_i\times d_iPi×di

代码:

/**************************************************************
    Problem: 4008
    User: MS42zz
    Language: C++
    Result: Accepted
    Time:4512 ms
    Memory:2000 kb
****************************************************************/
 
#include<bits/stdc++.h>
 
using namespace std ;
 
typedef double db;
 
const int N=300;
 
struct Tcard{db p,d;}card[N];
int n,r;
db f[N][N],g[N];
 
void init()
{
    scanf("%d%d",&n,&r);
    for (int i=1;i<=n;i++)   
        scanf("%lf%lf",&card[i].p,&card[i].d);
}
 
db qpow(db x,int y)
{
    db ret=1;
    for (;y;y>>=1,x=x*x)
        if (y&1) ret=ret*x;
    return ret;
}
 
void solve()
{
    memset(f,0,sizeof f);
    memset(g,0,sizeof g);
    f[0][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=min(i,r);j++)
            f[i][j]=(j> 0)*((1-qpow(1-card[i].p,r-(j-1)))*f[i-1][j-1])+
                    (j!=i)*(qpow(1-card[i].p,r-j)*f[i-1][j]);
    for (int i=1;i<=n;i++)
        for (int j=0;j<=min(i-1,r);j++)
            g[i]+=(1-qpow(1-card[i].p,r-j))*f[i-1][j];
    db ans=0;
    for (int i=1;i<=n;i++)
        ans+=g[i]*card[i].d;
    printf("%0.10lf\n",ans);
}
 
int main()
{
    #ifndef ONLINE_JUDGE
    freopen("4008.in","r",stdin);
    freopen("4008.out","w",stdout);
    #endif
     
    int T;scanf("%d",&T);
    for (int t=0;t<T;t++)
        init(),solve();
     
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值