BZOJ 4002 有意义的字符串
题目:
给定 b , d , n b,d,n b,d,n,求 ⌊ ( b + d 2 ) n ⌋ m o d    7528443412579576937 \lfloor(\frac{b+\sqrt{d}}{2})^n\rfloor\mod 7528443412579576937 ⌊(2b+d)n⌋mod7528443412579576937。
其中, 0 < b 2 ≤ d ≤ ( b + 1 ) 2 ≤ 1 0 18 , n ≤ 1 0 18 0<b^2\leq d\leq(b+1)^2\leq 10^{18},n\leq 10^{18} 0<b2≤d≤(b+1)2≤1018,n≤1018,并且 b m o d    2 = 1 , d m o d    4 = 1 b\mod 2=1,d\mod 4 =1 bmod2=1,dmod4=1
思路:
求这样一个东西的 n n n次方然后向下取整,再取模,这很显然是不能够直接快速幂的。所以对下取整里面的东西进行考虑。将 ( b + d 2 ) n (\frac{b+\sqrt{d}}{2})^n (2b+d)n二项式展开,会发现只有 d \sqrt d d的奇数次项是小数,其余都是整数。因此可以通过加上 ( b − d 2 ) n (\frac{b-\sqrt{d}}{2})^n (2b−d)n来消去 d \sqrt d d的奇数次项。
所以我们将所求转化为 ⌊ [ ( b + d 2 ) n + ( b − d 2 ) n ] − ( b − d 2 ) n ⌋ \lfloor [(\frac{b+\sqrt d}{2})^n+ (\frac{b-\sqrt d}{2})^n]- (\frac{b-\sqrt d}{2})^n \rfloor ⌊[(2b+d)n+(2b−d)n]−(2b−d)n⌋ 。
对于前半部分,这样的形式正好对应了二阶线性递推的通项的形式。
令 x 1 = b + d 2 , x 2 = b − d 2 x_1=\frac{b+\sqrt d}{2},x_2=\frac{b-\sqrt d}{2} x1=2b+d,x2=2b−d ,则原式前半部分变成 x 1 n + x 2 n x_1^n+x_2^n x1n+x2n
令 f ( n ) = x 1 n + x 2 n f(n)=x_1^n+x_2^n f(n)=x1n+x2n,则该函数的二阶线性递推式为
f ( n ) − ( x 1 + x 2 ) f ( n − 1 ) + ( x 1 x 2 ) f ( n − 2 ) = 0 f(n)-(x_1+x_2)f(n-1)+(x_1x_2)f(n-2)=0 f(n)−(x1+x2)f(n−1)+(x1x2)f(n−2)=0
化简得
f ( n ) = ( x 1 + x 2 ) f ( n − 1 ) − ( x 1 x 2 ) f ( n − 2 ) f(n)=(x_1+x_2)f(n-1)-(x_1x_2)f(n-2) f(n)=(x1+x2)f(n−1)−(x1x2)f(n−2)
f ( n ) = b f ( n − 1 ) + d − b 2 4 f ( n − 2 ) f(n)=bf(n-1)+\frac{d-b^2}{4}f(n-2) f(n)=bf(n−1)+4d−b2f(n−2)
对于后半部分,可以发现 ⌊ d ⌋ = b \lfloor\sqrt d\rfloor=b ⌊d⌋=b,所以 0 ≤ ∣ ( b − d ) n ∣ < 1 0\leq|(b-\sqrt d)^n|<1 0≤∣(b−d)n∣<1
关于正负性只需要看 n n n的奇偶性即可。
因此就能够巧妙地去掉了下取整的影响。
代码:
/**************************************************************
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 城池攻占
题目:
有 n n n座城池,每座城池有一个管辖城池 f i f_i fi,防御值 h i h_i hi和攻占后的提升值 a i , v i a_i,v_i ai,vi。有 m m m个骑士,每个骑士有一个战斗力 s i s_i si和一个初始目标城池 c i c_i ci。若骑士战斗力低于城池防御值,则骑士牺牲于改城池,否则骑士攻占成功。若骑士成功攻占城池 i i i,则获得战斗力提升。若参数 a i = 0 a_i=0 ai=0,则骑士战斗力 s i s_i si增加 v i v_i vi,若参数 a i = 1 a_i=1 ai=1,则骑士的战斗力乘上 v i v_i vi。骑士成功攻占城池 i i i,后会选择继续攻占城池 f i f_i fi,直到攻占完 1 1 1号城池。
思路:
维护一个数据结构支持快速插入,和删除最小值。左偏树显然可以,但这里写了启发式合并的大根堆(stl)。有一些细节需要注意,不能暴力修改骑士的战斗力值,因此需要在城池 i i i所在的堆上打标记,出入堆顶都要buff一下。关于启发式合并,直接使用一个指针数组表示城池 i i i的堆的编号是多少。
代码:
/**************************************************************
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 装备购买
题目:
有 n n n个 m m m维向量 z ⃗ ( a 0 , . . . . . . , a m ) \vec{z}(a_0,......,a_m) z(a0,......,am),每个向量有一个权值 c i c_i ci,若一个向量能够用已选择向量表示为 ∑ λ i z i ⃗ \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 × m n\times m n×m的数组 x i , j ( 1 ≤ i ≤ n ; 1 ≤ j ≤ m ) x_{i,j}(1\leq i\leq n;1\le j \le m) xi,j(1≤i≤n;1≤j≤m)。
对于 ∀ x i , j ∈ [ 0 , m ] \forall x_{i,j} \in [0,m] ∀xi,j∈[0,m];
对于 1 ≤ i ≤ n ; 1 ≤ j < m 1\leq i\le n;1\leq j<m 1≤i≤n;1≤j<m, s . t . x i , j < x i , j + 1 s.t. x_{i,j}<x_{i,j+1} s.t.xi,j<xi,j+1;
对于 1 < i ≤ n ; 1 ≤ j < m 1<i\leq n;1\leq j <m 1<i≤n;1≤j<m, s . t . x i , j < x i − 1 , j + 1 s.t. x_{i,j}<x_{i-1,j+1} s.t.xi,j<xi−1,j+1
求 x i , j x_{i,j} xi,j的方案数对 1 0 9 + 7 10^9+7 109+7取模的结果。
思路:
由题目可知,对于每一行 x i , j x_{i,j} xi,j,有且仅有一个分界点 k k k使得 x i , j = j − 1 ( j ≤ k ) , x i , j = j ( j > k ) x_{i,j}=j-1(j\leq k),x_{i,j}=j(j>k) xi,j=j−1(j≤k),xi,j=j(j>k)。
若第 i i i行的分界点为 k k k,则第 i + 1 i+1 i+1行的分界点一定大于等于 k − 1 k-1 k−1
设 f ( i , j ) f(i,j) f(i,j)表示第 i i i行的分界点在 j j j的方案数
显然有$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,j−1)+f(i−1,j+1)
转移如下图:
新建一些空节点使得转移变成如下图:
通过一一对应可以得到,上图中从
(
0
,
0
)
(0,0)
(0,0)到
(
n
+
m
+
1
,
n
)
(n+m+1,n)
(n+m+1,n)且不过直线
y
=
x
+
1
y=x+1
y=x+1和直线
y
=
x
−
m
−
2
y=x-m-2
y=x−m−2的路径数就是答案。要计算从
S
1
(
0
,
0
)
S_1(0,0)
S1(0,0)只往上往右走到
T
(
n
+
m
,
n
−
1
)
T(n+m,n-1)
T(n+m,n−1)的且不过直线
l
1
:
y
=
x
+
1
l_1:y=x+1
l1:y=x+1和直线
l
2
:
y
=
x
−
m
−
2
l_2:y=x-m-2
l2:y=x−m−2方案数,可以通过容斥计算。
用
S
1
S_1
S1到
T
T
T的路径数减去
S
1
S_1
S1关于
l
1
l_1
l1对称的对称点
S
2
S_2
S2到
T
T
T的路径。从
S
2
S_2
S2到
T
T
T的路径一定会经过直线
l
1
l_1
l1,但不保证不经过
l
2
l_2
l2,所以还要再减去作
S
1
S_1
S1关于
l
2
l_2
l2对称的对称点
S
3
S_3
S3到
T
T
T的路径数。但是这又会重复减去了既经过
l
1
l_1
l1,又经过
l
2
l_2
l2的部分。因此还要再加上
S
2
S_2
S2关于
l
2
l_2
l2对称的对称点到
T
T
T的路径数,和
S
3
S_3
S3关于
l
1
l_1
l1对称的对称点到
T
T
T的路径数。以此类推下去,直到对称点不在
T
T
T的左下方时停止。
代码:
/**************************************************************
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 管道连接
题目
该部门有 n n n个情报站,用 1 1 1到 n n n的整数编号。给出 m m m对情报站 u i ; v i u_i;v_i ui;vi和费用 w i w_i wi,表示情报站 u i u_i ui和 v i v_i vi之间可以花费 w i w_i wi单位资源建立通道。如果一个情报站经过若干个建立好的通道可以到达另外一个情报站,那么这两个情报站就建立了通道连接。形式化地,若 u i u_i ui和 v i v_i vi建立了通道,那么它们建立了通道连接;若 u i u_i ui和 v i v_i vi均与 t i t_i ti建立了通道连接,那么 u i u_i ui和 v i v_i vi也建立了通道连接。现在在所有的情报站中,有 p p p个重要情报站,其中每个情报站有一个特定的频道。问题是求需要花费最少的资源,使得任意相同频道的情报站之间都建立通道连接。
对于所有数据, 0 < c i ≤ p ≤ 10 ; 0 < u i , v i , d i ≤ n ≤ 1000 ; 0 ≤ m ≤ 3000 ; 0 ≤ w i ≤ 20000 0<c_i\le p\le 10;0<u_i,v_i,d_i\le n \le 1000;0\le m\le 3000;0\le w_i\le 20000 0<ci≤p≤10;0<ui,vi,di≤n≤1000;0≤m≤3000;0≤wi≤20000
思路:
只有 10 10 10个关键点,因此可以考虑设一个 2 10 2^{10} 210的状态来存储这些关键点的连通性。这可以用 O ( n 2 p ) 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 战争调度
题目:
王国里的公民每个公民有两个下属或者没有下属,这种关系刚好组成一个 n n n层的完全二叉树。公民 i i i的下属是 2 i 2i 2i和 2 i + 1 2i+1 2i+1。最下层的公民即叶子节点的公民是平民,平民没有下属,最上层的是国王,中间是各级贵族。现在这个王国爆发了战争,国王需要决定每一个平民是去种地以供应粮食还是参加战争,每一个贵族(包括国王自己)是去管理后勤还是领兵打仗。一个平民会对他的所有直系上司有贡献度,若一个平民 i i i参加战争,他的某个直系上司 j j j领兵打仗,那么这个平民对上司的作战贡献度为 w i , j w_{i,j} wi,j。若一个平民 i i i种地,他的某个直系上司 j j j管理后勤,那么这个平民对上司的后勤贡献度为 f i , j f_{i,j} fi,j,若 i i i和 j j j所参加的事务不同,则没有贡献度。为了战争需要保障后勤,国王还要求不多于 m m m个平民参加战争。整个王国所有贵族得到的贡献度最大的最大值。
对于所有数据, 2 ≤ n ≤ 10 ; m ≤ 2 n − 1 ; 0 ≤ w i , j , f i , j ≤ 2000 2\le n\le 10;m\le 2^{n}-1;0\leq w_{i,j},f_{i,j}\le 2000 2≤n≤10;m≤2n−1;0≤wi,j,fi,j≤2000
思路:
对于每个平民,他能影响的贵族只有 n n n个,考虑用 2 n 2^n 2n枚举这 n n n个贵族的状态,可以得到每个平民对父链所有状态的影响。考虑如何进行贡献,首先,平民对所有贵族的贡献已经计算了,所以考虑某个贵族的状态的时候不需要再根据平民的状态来计算自身的贡献。在父链相同的情况下,取贵族的的两种贡献的最大值作为当前为根的子树的最大值。向上贡献就直接将最大值加入父亲的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 亚瑟王
题目:
有 n n n张卡牌排列成某种顺序,依次编号为 1 1 1到 n n n,顺序即为输入。第 i i i张卡牌的发动概率为 p i p_i pi,如果发动,会对敌方造成 d i d_i di点伤害, 0 < p i < 1 0<p_i<1 0<pi<1,
共有
r
r
r轮游戏,每轮中:
如果这张卡牌在这一局游戏中已经发动过技能,则丢弃这张卡牌;否则(这张卡牌在这一局游戏中没有发动过技能),设这张卡牌为第
i
i
i张,则将其以 pi的
概率发动技能:如果技能发动,则对敌方造成 d i d_i di点伤害,并结束这一轮,否则继续考虑下一张卡牌,如果这张卡牌已经是最后一张,则结束这一轮。
错误思路:
首先直观感觉考虑第 i i i张卡牌在第 j j j轮中发动的概率,发现这样考虑会受到前面的卡牌是否被选择的影响,并且这种影响不具有一般性。头铁继续考虑下去
,将轮数倒过来考虑,会神奇的发现——每次的选择会影响之前的概率。假如在这一次选择了第 i i i张卡牌,则第 j ( i < j ≤ n ) j(i<j\le n) j(i<j≤n)张卡牌选择的概率(期望)会
乘上
(
1
−
p
i
)
(1-p_i)
(1−pi),再结合选择后面以及选择它自己对它的影响,就能递推求出第
r
r
r轮的期望和了吧…吧。
其实,这是错误的。因为第
i
i
i张卡牌对第
j
j
j张卡牌的影响的概率应该是累加的,而不是累乘的。这样计算会导致第
i
i
i张卡牌不出现的概率多次影响第
j
j
j张
卡牌。至于为什么要分享这一心路历程,个人认为也是一种不错的收获。
思路:
直接考虑第 i i i张卡牌在轮次中的影响不可做,那么有一种比较套路的想法就是将影响一般化,使得这些影响可以用状态的增减来简单表示。直接考虑第
i i i张卡牌在 r r r轮中被选中的概率。第 i i i张卡牌在 r r r轮选择中都没有被选到的概率为 ( 1 − p i ) r (1-p_i)^r (1−pi)r,当然这个概率受到前面卡牌的影响,但是这样的概率被
一般化了。引入状态 j j j,用 f ( i , j ) f(i,j) f(i,j)表示在 r r r轮中,前 i i i张卡牌被选了 j j j张的概率。如何进行转移呢?考虑第 i + 1 i+1 i+1张卡牌在 f ( i , j ) f(i,j) f(i,j)状态下被选择的概
率 P = f ( i , j ) ∗ ( 1 − ( 1 − p i + 1 ) r − j ) P=f(i,j)*(1-(1-p_{i+1})^{r-j}) P=f(i,j)∗(1−(1−pi+1)r−j),因此 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)。求出 f f f后,再求每张卡牌在
r r r轮中被选中的概率就好求了。 P i = ∑ j = 0 m i n ( i − 1 , r ) ( 1 − ( 1 − p i ) 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(i−1,r)(1−(1−pi)r−j)×f(i−1,j),所以期望伤害就是 ∑ P i × d i \sum P_i\times d_i ∑Pi×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;
}