noip 2018 模拟赛17

T 1 T_1 T1——grasshopper(3085)

Description:

在一个 n ⋅ n n\cdot n nn的矩形中,从第 R R R行第 C C C列开始。按照以下规则遍历:
(1):跳到相邻的行,并且列坐标之差要大于 1 1 1,即 ∣ r 1 − r 2 ∣ = 1 |r_1−r_2|=1 r1r2=1 ∣ c 1 − c 2 ∣ > 1 |c_1−c_2|>1 c1c2>1
(2):跳到相邻的列,并且行坐标之差要大于 1 1 1,即 ∣ c 1 − c 2 ∣ = 1 |c_1−c_2|=1 c1c2=1 ∣ r 1 − r 2 ∣ > 1 |r_1−r_2|>1 r1r2>1
(3):下一个数要大于当前数。
求最多遍历多少个数。
n ≤ 1500 , A i , j ≤ 1 0 6 n\le1500,A_{i,j}\le10^6 n1500,Ai,j106

Solution:

  • 观察第3个条件,发现这个转移一定是单调的,那么就可以 d p dp dp
  • 我们可以定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示到第 i i i行第 j j j列的最大个数。
  • 又因为转移单调,我们可以建边,倒着转移。
  • 再观察第1、2条件,发现我们每次转移到 ( x , y ) (x,y) (x,y)时,我们只观察 ( x + 1 , y ) , ( x − 1 , y ) , ( x , y + 1 ) , ( x , y − 1 ) (x+1,y),(x-1,y),(x,y+1),(x,y-1) (x+1,y),(x1,y),(x,y+1),(x,y1) d p dp dp值。

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i=i##_end_;--i)
templateinline bool chkmin(T&x,T y){return x>y?x=y,1:0;}
templateinline bool chkmax(T&x,T y){return xinline void rd(T&x){
	x=0;char c;
	while((c=getchar())<48);
	do x=(x<<1)+(x<<3)+(c^48);
	while((c=getchar())>47);
}

const int K=4,M=1505,N=1000005;
int n,qwq,R,C;
int A[M][M],dp[M][M];

struct node{
	int x,y,val;
	bool operator<(node const&_)const{
		return val<_.val;
	}
	bool check(int const&_x,int const&_y){
		return (abs(x-_x)==1 and abs(y-_y)>1) or (abs(y-_y)==1 and abs(x-_x)>1);
	}
}f[M][5],g[M][5];

int head[N],nxt[M*M],X[M*M],Y[M*M];
void addedge(int u,int x,int y){
	X[qwq]=x,Y[qwq]=y;
	nxt[qwq]=head[u];
	head[u]=qwq++;
}
#define EREP(x) for(int i=head[x];~i;i=nxt[i])

int main(){
//	freopen("grasshopper.in","r",stdin);
//	freopen("grasshopper.out","w",stdout);
	rd(n),rd(R),rd(C);
	memset(head,-1,sizeof(head));
	REP(i,1,n) REP(j,1,n) rd(A[i][j]),addedge(A[i][j],i,j);
	
	DREP(x,N-1,A[R][C]) {
		EREP(x){
			int x=X[i],y=Y[i];
			SREP(k,0,4){
				if(f[x-1][k].check(x,y)) chkmax(dp[x][y],f[x-1][k].val+1);
				if(f[x+1][k].check(x,y)) chkmax(dp[x][y],f[x+1][k].val+1);
				if(g[y-1][k].check(x,y)) chkmax(dp[x][y],g[y-1][k].val+1);
				if(g[y+1][k].check(x,y)) chkmax(dp[x][y],g[y+1][k].val+1);
			}
			chkmax(dp[x][y],1);
		}
		EREP(x){
			int x=X[i],y=Y[i];
			node a=(node){x,y,dp[x][y]};
			node b=(node){x,y,dp[x][y]};
			SREP(k,0,K) if(f[x][k]

T 2 T_2 T2——draw(1692)

Description:

n n n个点, K K K种颜色,现在有 n n n个条件,形如第 i i i个点不能与第 f i f_i fi个点同色。求染色方案数。对1e9+7取模
n , K ≤ 1 0 6 n,K\le10^6 n,K106

Solution:

  • 比较常规的题目。
  • n n n个条件下,图中只包含树和基环树组合成的森林。
  • 分类讨论。
  • 对于单纯的树,显然除了根有 K K K种颜色选择,接下来的儿子都被它的父亲影响,其它点都是 K − 1 K-1 K1种颜色选择。
  • 对于基环树,常规的抽出环,染色问题嘛,那么就破环为链。
  • 考虑 d p [ i ] [ 0 / 1 ] dp[i][0/1] dp[i][0/1]表示前 i i i个点相邻不同,最后一个点与第1个点相同/不相同的方案数。
  • 转移就有 d p [ i ] [ 0 ] = ( K − 2 ) × d p [ i − 1 ] [ 0 ] + ( K − 1 ) × d p p [ i − 1 ] [ 1 ] , d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] dp[i][0]=(K-2) \times dp[i-1][0]+(K-1) \times dpp[i-1][1],dp[i][1]=dp[i-1][0] dp[i][0]=(K2)×dp[i1][0]+(K1)×dpp[i1][1],dp[i][1]=dp[i1][0]
  • 环上的每个点的子树与上述单纯的树同理。

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i=i##_end_;--i)
#define ll long long
templateinline bool chkmin(T&x,T y){return x>y?x=y,1:0;}
templateinline bool chkmax(T&x,T y){return xinline void rd(T&x){
	x=0;char c;
	while((c=getchar())<48);
	do x=(x<<1)+(x<<3)+(c^48);
	while((c=getchar())>47);
}
const int N=1e6+2,mod=1e9+7;

int n,K;

int qwq,head[N];
struct edge{
	int to,nxt;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++;}
#define EREP(x) for(int i=head[x];~i;i=E[i].nxt)

struct p30{
	int col[502];
	int ans;
	
	bool flag;
	void dfs(int x){
		if(x==n+1){
			++ans;
			return;
		}
		REP(j,1,K){
			flag=1;
			EREP(x) if(j==col[E[i].to]) {flag=0;break;}
			if(!flag)continue;
			col[x]=j;
			dfs(x+1);
			col[x]=0;
		}
	}
	
	void solve(){
		dfs(1);
		printf("%d\n",ans);
	}
}p1;

int cnt;
bool mark[N];
ll Pow(ll a,ll b){
	ll x=1;
	while(b){
		if(b&1)x=x*a%mod;
		a=a*a%mod,b>>=1;
	}
	return x;
}

struct p100{
	
	int dfn[N],low[N],tim;
	int stk[N],top;
	int sz[N],tot;
	bool vis[N];
	int num;
	
	void tarjan(int x){
		dfn[x]=low[x]=++tim;
		stk[++top]=x;
		vis[x]=1;
		EREP(x){
			int y=E[i].to;
			if(!dfn[y]){
				tarjan(y);
				chkmin(low[x],low[y]);
			}
			else if(vis[y]) chkmin(low[x],dfn[y]);
		}
		if(dfn[x]==low[x]){
			if(stk[top]!=x){
				tot++;
				do{
					num++;
					sz[tot]++;
					vis[stk[top]]=0;
				}while(x!=stk[top--]);
			}
			else top--;
		}
	}
	
	ll dp[N][2];
	
	void Init(){
		dp[1][1]=1;
		SREP(i,2,N){
			dp[i][0]=(dp[i-1][0]*(K-2)%mod+dp[i-1][1]*(K-1)%mod)%mod;
			dp[i][1]=dp[i-1][0];
		}
	}
	
	void solve(){
		REP(i,1,n) if(!mark[i] and !dfn[i]) tarjan(i);
		Init();
		ll ans=1;
		ans=Pow(K,cnt)*Pow(K-1,n-cnt-num)%mod;
		REP(i,1,tot) ans=ans*dp[sz[i]][0]%mod*K%mod;
		printf("%lld\n",ans);
	}
}p2;

int main(){
//	freopen("draw.in","r",stdin);
//	freopen("draw.out","w",stdout);
	rd(n),rd(K);
	memset(head,-1,sizeof head);
	REP(i,1,n){
		int f;rd(f);
		if(i!=f) addedge(i,f);
		else mark[i]=1,cnt++;
	}
	
//	if(n<=15 and K<=3)p1.solve();
//	else 
	p2.solve();
	return 0;
}

T 3 T_3 T3——treecnt

Description:

一棵树,定义 T r e e [ L , R ] Tree[L,R] Tree[L,R]表示连接 [ L , R ] [L,R] [L,R]的点需要的最少的边数。求 ∑ L = 1 n ∑ R = L n T r e e [ L , R ] \sum_{L=1}^{n}\sum_{R=L}^{n}Tree[L,R] L=1nR=LnTree[L,R]
n ≤ 1 0 5 n\le10^5 n105

Solution:

  • 对于这种求 2 2 2 ∑ \sum 的问题,一般都是求每个分量的贡献,最后统计答案。
  • 对于这道树上的问题,我们显然是求每条边的贡献。
  • 观察 T r e e Tree Tree的定义,我们发现对于一条边,对于一个 T r e e [ L , R ] Tree[L,R] Tree[L,R],当且仅当该边的子树下有点 x ∈ [ L , R ] x \in[L,R] x[L,R],该边的子树外有点 y ∈ [ L , R ] y \in[L,R] y[L,R]
  • 我们定义子树内的点看做1,子树外的点看做0,那么可以产生贡献的区间会同时包含0和1。
  • 那么我们可以直接上线段树来维护,对于父亲和儿子,我们可以线段树合并。
  • 这样发复杂度为 Θ ( n log ⁡ n ) \Theta(n\log n) Θ(nlogn)

Code:

#include
using namespace std;
#define REP(i,f,t) for(int i=(f),i##_end_=(t);i<=i##_end_;++i)
#define SREP(i,f,t) for(int i=(f),i##_end_=(t);i=i##_end_;--i)
#define ll long long
templateinline bool chkmax(T&x,T y){return xinline void rd(T&x){
	x=0;char c;
	while((c=getchar())<48);
	do x=(x<<1)+(x<<3)+(c^48);
	while((c=getchar())>47);
}

const int N=1e5+2,NN=302;

int n;

int qwq,head[N];
struct edge{
	int to,nxt;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++;}
#define EREP(x) for(int i=head[x];~i;i=E[i].nxt)


struct p40{
	
	int fa[3002];
	bool mark[3002];
	
	void dfs(int x,int f){
		fa[x]=f;
		EREP(x) if(E[i].to!=f) dfs(E[i].to,x);
	}
	
	void solve(){
		ll ans=0;
		REP(L,1,n){
			dfs(L,0);
			memset(mark,0,sizeof mark);
			mark[L]=1;
			int res=0;
			REP(R,L+1,n){
				for(int i=R;!mark[i];i=fa[i]) mark[i]=1,res++;
				ans+=res;
			}
		}
		printf("%lld\n",ans);
	}
}p1;

int deg[N];
struct pline{
	int s;
	bool check(){
		DREP(i,n,1){
			if(deg[i]>2)return 0;
			if(deg[i]==1)s=i;
		}
		return 1;
	}
	
	void solve(){
		
		
	}
}p2;

struct p100{
	
	int rt[N],tat;
	int Lson[N*20],Rson[N*20];
	struct node{
		int len;
		int l1,r1,l2,r2;
		ll sum;
		node(){len=l1=r1=l2=r2=sum=0;} 
	}tree[N*20];
	
	void Merge(node &A,node L,node R){
		A.len=L.len+R.len;
		A.l1=L.l1;
		A.r1=R.r1;
		A.l2=L.l2;
		A.r2=R.r2;
		if(L.l1==L.len) A.l1+=R.l1;
		if(L.l2==L.len) A.l2+=R.l2;
		if(R.r1==R.len) A.r1+=L.r1;
		if(R.r2==R.len) A.r2+=L.r2; 
		A.sum=L.sum+R.sum+1ll*L.len*R.len-1ll*L.r1*R.l1-1ll*L.r2*R.l2;
	}
	
	void Up(int p){
		if(Lson[p] and Rson[p]) Merge(tree[p],tree[Lson[p]],tree[Rson[p]]);
		else if(Lson[p]){
			node tmp;
			tmp.l1=tmp.r1=tmp.len=tree[p].len-tree[Lson[p]].len;
			Merge(tree[p],tree[Lson[p]],tmp);
		}
		else if(Rson[p]){
			node tmp;
			tmp.l1=tmp.r1=tmp.len=tree[p].len-tree[Rson[p]].len;
			Merge(tree[p],tmp,tree[Rson[p]]);
		}
	}
	
	void update(int &p,int l,int r,int x){
		p=++tat;
		tree[p].len=r-l+1;
		if(l==r){
			tree[p].l2=tree[p].r2=1;
			return;
		}
		int mid=(l+r)>>1;
		if(x<=mid)update(Lson[p],l,mid,x);
		else update(Rson[p],mid+1,r,x);
		Up(p);
	}
	
	int merge(int x,int y){
		if(!x or !y) return x|y;
		Lson[x]=merge(Lson[x],Lson[y]);
		Rson[x]=merge(Rson[x],Rson[y]);
		Up(x);
		return x;
	}
	
	ll ans;
	
	void dfs(int x,int f){
		update(rt[x],1,n,x);
		EREP(x){
			int y=E[i].to;
			if(y==f)continue;
			dfs(y,x);
			rt[x]=merge(rt[x],rt[y]);
		}
		ans+=tree[rt[x]].sum;
	}
	
	void solve(){
		dfs(1,0);
		printf("%lld\n",ans);
	}
}p3;

int main(){
//	freopen("treecnt.in","r",stdin);
//	freopen("treecnt.out","w",stdout);
	rd(n);
	memset(head,-1,sizeof head);
	SREP(i,1,n){
		int a,b;
		rd(a),rd(b);
		addedge(a,b);
		addedge(b,a);
//		++deg[a],++deg[b];
	}

	if(n<=3000)p1.solve();
//	else if(p2.check())p2.solve();
	else p3.solve();
	 
	return 0;
}

你可能感兴趣的:(dp,染色问题,基环树,线段树合并,离线赛-总结)