【NOIP2018补题】D2T2 填数游戏 D2T3 保卫王国

D2T2填数游戏

【题目】
原题地址

【解题思路】
个人认为这次noip最难的题目

以下来自这里
首先我们显然是要对一条斜线进行考虑,那么考虑现在什么情况才是合法的。

  • 同一个对角线要么全是 0 0 0,要么全是 1 1 1,要么下面一段全是 1 1 1而上面一段全是 0 0 0
  • ∀ 1 < i ≤ n , 1 ≤ j < m \forall 1<i\leq n,1\leq j<m 1<in,1j<m,若 ( i , j ) (i,j) (i,j) ( i − 1 , j + 1 ) (i-1,j+1) (i1,j+1)填的数相同,那么 ( i , j + 1 ) (i,j+1) (i,j+1)及右下角所有方块中,一条斜线上的数需要全相等。
    那么现在我们可以得到一个状压 DP \text{DP} DP的思路:
    f i , j , S f_{i,j,S} fi,j,S表示从下(右)往左(上)前 i i i个对角线,第 i i i个对角线放了 j j j 1 1 1。而 S S S是一个集合,若第 i i i个对角线第 k k k个格子及右下角子矩阵的每个对角线都满足其中的数全相等则 S S S中包含 k k k
    我们枚举一个 k k k,转移 f i − 1 , j , S ⇒ f i , k , T f_{i-1,j,S}\Rightarrow f_{i,k,T} fi1,j,Sfi,k,T,其中 T T T可以由 j , k , S j,k,S j,k,S计算得出,并进行判断是否合法。
    接下来找规律,当 n + 2 ≤ m n+2\leq m n+2m,则 F ( n , m ) = 3 × F ( n , m − 1 ) F(n,m)=3\times F(n,m-1) F(n,m)=3×F(n,m1)
    理论复杂度 O ( 2 n n 4 + l o g m ) O(2^nn^4+log m) O(2nn4+logm),可以判掉很多无用状态。

还有一种找规律的解法看这里

【参考代码】

状压

#include
using namespace std;

typedef long long ll;
const int mod=1e9+7;
int n,m,len,ans,Am;
int t[40],f[2][40][(1<<8)+10];

int qpow(int x,int y)
{
	int ret=1;
	for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) ret=(ll)ret*x%mod;
	return ret;
}
int get(int x,int y){return (x>>y-1)&1;}
int le(int x,int y){return len-x+1>=n?y-1:y;}
int ri(int x,int y){return len-x+1>=n?y:y+1;}
void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP5023.in","r",stdin);
	freopen("LGP5023.out","w",stdout);
#endif
	scanf("%d%d",&n,&m);Am=m;
	if(n==1){printf("%d\n",qpow(2,m));return 0;}
	if(m>n+1) m=n+1; len=n+m-1; 
	f[1][1][1]=f[1][0][1]=1;
	for(int i=1;i<=n;++i) t[len-i+1]=min(i,m);
	for(int i=2;i<=m;++i) t[len-(i+n-1)+1]=min(n,m-i+1);
	for(int i=2;i<=len;++i)
	{
		int op=i&1;
		for(int j=0;j<=n;++j) for(int k=0;k<1<<n;++k) f[op][j][k]=0;
		for(int j=0;j<=t[i-1];++j) for(int k=0;k<1<<t[i-1];++k) if(f[op^1][j][k])
		{
			for(int l=0;l<=t[i];++l)
			{
				bool flag=1;
				for(int p=1;p<t[i];++p) if(p^l && !get(k,ri(i,p))){flag=0;break;}
				if(!flag) continue;
				int S=0;
				for(int p=1;p<=t[i];++p)
				{
					int kl=le(i,p),kr=ri(i,p);
					if(kl<1 || kl>t[i-1]) {if(get(k,kr))S|=1<<p-1;}
					else if(kr<1 || kr>t[i-1]) {if(get(k,kl))S|=1<<p-1;}
					else if(get(k,kl) && get(k,kr) && j^kl) S|=1<<p-1;
				}
				up(f[op][l][S],f[op^1][j][k]);
			}
		}
	}
	for(int i=0;i<2;++i) for(int j=0;j<2;++j) up(ans,f[len&1][i][j]);
	if(Am>m) ans=(ll)ans*qpow(3,Am-m)%mod;
	printf("%d\n",ans);

	return 0;
}

找规律(矩阵用于前50pt)

#include
using namespace std;

typedef long long ll;
const int mod=1e9+7,M=10;
int n,m,ans,op1,op2,op3,op4,S,Am,res;
int f[10];

void up(int &x,int y){x+=y;if(x>=mod)x-=mod;}
int qpow(int x,int y)
{
	int ret=1;
	for(;y;y>>=1,x=(ll)x*x%mod) if(y&1) ret=(ll)ret*x%mod;
	return ret;
}

struct Matrix
{
    int a[M][M];
    void clear(){memset(a,0,sizeof(a));}
    void one(){clear();for(int i=0;i<S;++i)a[i][i]=1;}
}mat,Ans;

bool check(int x,int y)
{
    for(int i=1;i<n;++i) if(((x>>i)&1)<((y>>(i-1))&1)) return 0;
    return 1;
}

Matrix mul(Matrix const&x,Matrix const&y)
{
    Matrix ret;ret.clear();
    for(int i=0;i<S;++i) for(int j=0;j<S;++j) for(int k=0;k<S;++k)
        up(ret.a[i][j],(ll)x.a[i][k]*y.a[k][j]%mod);
    return ret;
}

Matrix qpow(Matrix x,int y)
{
    Matrix ret;ret.one();
    for(;y;y>>=1,x=mul(x,x)) if(y&1) ret=mul(ret,x);
    return ret;
}

int solution1()
{
    S=(1<<n);Am=m;m=min(m,n);
    for(int i=0;i<S;++i) for(int j=0;j<S;++j) if(check(i,j)) mat.a[i][j]=1;
    for(int i=0;i<S;++i) Ans.a[i][i]=1;
    Ans=mul(Ans,qpow(mat,m-1));
    for(int i=0;i<S;++i) for(int j=0;j<S;++j) up(res,Ans.a[i][j]);
    if(n==3 && Am>=3) res=112;
    res=(ll)res*qpow(3,Am-m)%mod;
    printf("%d\n",res);
}

void solution2()
{
	for(int i=1;i<=n-4;++i) f[i+1]=(ll)(4*5+f[i]*4)%mod;
	ans=(ll)qpow(2,n)*(5*3+f[n-3]*2)%mod;
	op1=(ll)qpow(4,n-2)*qpow(2,n-1)%mod*2*2%mod;
	op2=(ll)qpow(4,n-4)*qpow(2,n-1)%mod*2*2*5%mod;
	if(n==m) ans=(ll)(ans+op1+op2)%mod;
	else
	{
		op3=(ll)(f[n-3]*3+4*5)*qpow(2,n)%mod;
		op4=(ll)((f[n-3]*3+4*4)*qpow(2,n)%mod+4*3*qpow(2,n-1)%mod)%mod;
		ans=(ll)((op1+op2)*3+op3+op4)%mod*qpow(3,m-n-1)%mod;
	}
	printf("%d\n",ans);
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP5023.in","r",stdin);
	freopen("LGP5023.out","w",stdout);
#endif
	scanf("%d%d",&n,&m);
	if(n<=3) solution1();
	else solution2();
	return 0;
}

D2T3 保卫王国

【题目】
原题地址

【解题思路】
NOIP \text{NOIP} NOIP考的 ddp \text{ddp} ddp裸题。
首先考虑暴力的 dp \text{dp} dp,我们设 f u , 0 / 1 f_{u,0/1} fu,0/1表示这一位选或不选得到的最小花费。
如果当前点 u u u不选,那么它的所有儿子都要选,所以 f u , 0 = ∑ f v , 1 f_{u,0}=\sum f_{v,1} fu,0=fv,1
如果当前点 u u u选,那么它的儿子可以选也可以不选,所以 f u , 1 = w u + ∑ min ⁡ { f v , 0 , f v , 1 } f_{u,1}=w_u+\sum \min \{ f_{v,0},f_{v,1}\} fu,1=wu+min{fv,0,fv,1}

现在要求支持独立的修改,那么考虑 ddp \text{ddp} ddp,我们需要将转移写成矩阵的形式。观察到转移方程是 ( min ⁡ , + ) (\min,+) (min,+)的形式,所以是可以写出矩阵的,然后就是一个 ( min ⁡ , + ) (\min,+) (min,+)的矩阵乘法。

由于要向上转移,我们还需要定义另一个 dp \text{dp} dp数组 g v , 0 / 1 g_{v,0/1} gv,0/1表示 v v v这个节点去掉后它父亲的 dp \text{dp} dp值。这个值我们用 f f f数组求一次和就可以得到。

我们现在将一个节点 v v v dp \text{dp} dp值看作一个矩阵,那么就是
[ f v , 0 f v , 1 ] \begin{bmatrix} f_{v,0}& f_{v,1} \end{bmatrix} [fv,0fv,1]
如果我们向上转移到 u u u的话,那么就是右乘转移矩阵
[ ∞ g v , 1 g v , 0 g v , 1 ] \begin{bmatrix} \infty &g_{v,1} \\ g_{v,0} & g_{v,1} \end{bmatrix} [gv,0gv,1gv,1]

以上是大概的思路。
对于这道题的询问,我们先按倍增求 l c a lca lca的套路向上跳, 跳的时候沿途乘转移矩阵。

如果这两个点原先不是祖先后代关系的话那么最后会跳到同一个节点的两个儿子
然后根据这两个儿子的新 dp \text{dp} dp值计算出它们 l c a lca lca的新 dp \text{dp} dp值 ,然后跳到根。

如果原先是祖先后代关系那么就直接把深的点向上跳到浅的点的位置,然后再跳到根即可。

总的复杂度就是 O ( ( n + q ) log ⁡ n ) O((n+q)\log n) O((n+q)logn)

【参考代码】

#include
using namespace std;

typedef long long ll;
const ll INF=(ll)0x3f3f3f3f3f3f3f3f;
const int N=1e5+10;
int n,m,tot;
int head[N],Log[N],dep[N],fa[20][N],fc[20];
ll w[N],f[N][2];
char NOUSE[5];

int read()
{
	int ret=0;char c=getchar();
	while(!isdigit(c)) c=getchar();
	while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
	return ret;
}

struct Tway{int v,nex;}e[N<<1];
void adde(int u,int v){e[++tot]=(Tway){v,head[u]};head[u]=tot;}
void addedge(int u,int v){adde(u,v);adde(v,u);}
void gmin(ll &x,ll y){x=min(x,y);}
ll up(ll x,ll y){return x+y>=INF?INF:x+y;}

struct Matrix
{
	ll a[2][2];
	void clear(){memset(a,0x3f3f,sizeof(a));}
	void init(ll x,ll y){a[0][0]=INF;a[0][1]=a[1][1]=y;a[1][0]=x;}
	Matrix operator * (Matrix const&x)
	{
		Matrix ret;ret.clear();
		for(int i=0;i<2;++i) for(int j=0;j<2;++j) for(int k=0;k<2;++k)
			gmin(ret.a[i][j],up(a[i][k],x.a[k][j]));
		return ret;
	}
}mat[20][N];

struct DP
{
	ll a[2];
	void clear(){memset(a,0x3f3f,sizeof(a));}
	void init(ll x,ll y){a[0]=x;a[1]=y;}
};
DP mul(const DP&x,const Matrix&y)
{
	DP ret;ret.clear();
	for(int i=0;i<2;++i) for(int j=0;j<2;++j) gmin(ret.a[i],up(x.a[j],y.a[j][i]));
	return ret;
}

void dfs1(int x)
{
	f[x][1]=w[x];
	for(int i=head[x];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(v==fa[0][x]) continue;
		fa[0][v]=x;dep[v]=dep[x]+1;dfs1(v);
		f[x][0]+=f[v][1];f[x][1]+=min(f[v][0],f[v][1]);
	}
}

void dfs2(int x)
{
	for(int i=1;fc[i]<dep[x];++i)
		fa[i][x]=fa[i-1][fa[i-1][x]],mat[i][x]=mat[i-1][x]*mat[i-1][fa[i-1][x]];
	for(int i=head[x];i;i=e[i].nex)
	{
		int v=e[i].v;
		if(v==fa[0][x]) continue;
		mat[0][v].init(f[x][0]-f[v][1],f[x][1]-min(f[v][0],f[v][1]));
		dfs2(v);
	}
}

ll work(int u,int x,int v,int y)
{
	if(dep[u]<dep[v]) swap(u,v),swap(x,y);
	//printf("%d %d %d %d\n",u,x,v,y);
	DP t1,t2;t1.init(f[u][0],f[u][1]);t2.init(f[v][0],f[v][1]);
	t1.a[x^1]=t2.a[y^1]=INF;
	//printf("%lld %lld\n",t1.a[0],t1.a[1]);
	for(int t=dep[u]-dep[v],i=0;i<19;++i)
		if(t&fc[i]) t1=mul(t1,mat[i][u]),u=fa[i][u];
	//printf("%lld %lld\n",t1.a[0],t1.a[1]);
	int l;DP t;
	if(u^v)
	{
		for(int i=18;~i;--i) if(fa[i][u]^fa[i][v])
		{	
			t1=mul(t1,mat[i][u]);u=fa[i][u];
			t2=mul(t2,mat[i][v]);v=fa[i][v];
		}
		l=fa[0][u];
		t.init(up(f[l][0]-f[u][1]-f[v][1],up(t1.a[1],t2.a[1])),
			   up(f[l][1]-min(f[u][0],f[u][1])-min(f[v][0],f[v][1]),
			   up(min(t1.a[0],t1.a[1]),min(t2.a[0],t2.a[1]))));
	}
	else l=u,t=t1,t.a[y^1]=INF;
	//printf("%lld %lld\n",t.a[0],t.a[1]);
	for(int i=18;~i;--i) if(fa[i][l]) 
		t=mul(t,mat[i][l]),l=fa[i][l];
	ll ret=min(t.a[0],t.a[1]);
	return ret>=INF?-1:ret;
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("LGP5024.in","r",stdin);
	freopen("LGP5024.out","w",stdout);
#endif
	Log[1]=0;for(int i=1;i<N;++i) Log[i]=Log[i>>1]+1;
	fc[0]=1;for(int i=1;i<19;++i) fc[i]=fc[i-1]<<1;
	n=read();m=read();scanf("%s",NOUSE);
	for(int i=1;i<=n;++i) w[i]=read();
	for(int i=1;i<n;++i) addedge(read(),read());
	dep[1]=1;dfs1(1);dfs2(1);
	// for(int i=1;i<=n;++i,puts("")) 
	// 	for(int a=0;a<2;++a) for(int b=0;b<2;++b) printf("%lld ",mat[1][i].a[a][b]);
	while(m--)
	{
		int a=read(),b=read(),c=read(),d=read();
		printf("%lld\n",work(a,b,c,d));
	}
	return 0;
}

你可能感兴趣的:(NOIP,DP-矩阵乘法,DP-动态DP,DP-状态压缩)