NOIP2018 部分题解

心血来潮看了看18年的联赛,感觉自己进步很大= =

Day1
T1
对于一个“波”来说,显然需要的次数为最大的数(波峰),对于多个“波”,就每次记录一下从波底到波峰的高度差即可,这可以用差分简单实现

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn=100005;

int a[maxn], n;

signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	int ans = a[1];
	for(int i=2;i<=n;i++){
		if(a[i] > a[i-1]){
			ans += a[i] - a[i-1];
		}
	}
	printf("%d\n",ans);

	return 0;
}

T2
显然两种系统\(a,b\)等价当且仅当\(a\)中的数字都能被\(b\)中的数字通过线性组合得到,对1~25000的数跑一个类似于完全背包的dp即可

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Madoka"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f;

int n;
int dp[25005],a[105];

void solve(){
	memset(dp,0,sizeof dp);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	sort(a+1,a+n+1);
	int ans = 0;
	for(int i=1;i<=n;i++){
		if(dp[a[i]])continue;
		++ ans;
		dp[a[i]] = 1;
		for(int j=1;a[i]+j<=25000;j++)dp[a[i] + j] |= dp[j];
	}
	printf("%d\n",ans);
}

signed main(){
	int te;scanf("%d",&te);
	while(te --)solve();

	return 0;
}

T3
给一个带边权的树,要划分出\(m\)个边不相交的路径,使得路径权值的最小值最大
首先二分答案mid,考虑树形dp,需要维护一个当前结点为根的子树的答案,以及在这种情况下能够向上传的路径权值的最大值
\(dp[i]\)表示以 i 为根的答案,\(mx[i]\)表示 i 结点的子树能向上传的路径权值的最大值(dfs时顺便加上(i,fa[i])的权值)
\(dp[i] = \sum dp[u] + C\),其中 C 需要我们将每个儿子向上传的权值两两配对,使权值达到mid,以及在答案最优时向上传的权值要最大
首先想到的是暴力枚举向上传的边,这里有一个精妙的实现:for(int i=-1;i<(int)mp.size();i++),这样就涵盖了恰好两两匹配的情况。注意(int)必加!!因为\(vector<>.size()\)是unsigned,不能判断负数,时间复杂度是非常不满的O(n^2logn),结合树的直径可以拿到90pts的好成绩
发现枚举向上传的边这一步是有单调性的,很明显如果上传了边权为w的边,则比w小的边也一定可以上传,于是可以二分上传的边,时间复杂度大概是两个log,可以过
注意vector a(100) 如果再push_back,a.size() 会变成 101

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <set>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,maxn=50005;

int n,m;
vector<pii>g[maxn];
int dp[maxn], mx[maxn];

void dfs(int x,int lim,int fat = -1){
	if(~fat && g[x].size() == 1)return ;
	vector<int> mp;
	for(pii now : g[x]){
		int u = now.first, w = now.second;
		if(u == fat)continue;
		dfs(u, lim, x);
		mx[u] += w;
		if(mx[u] >= lim)++ dp[x];	// 自成一派 
		else mp.push_back(mx[u]);
		dp[x] += dp[u];
	}
	sort(mp.begin(),mp.end());
	int mpc = mp.size();
	int curid = -2, curmx = -inf;
	int le = 0, ri = mpc-1, rans;
	
	// 单独考虑全配对情况 
	int l=0;
	int r=mpc-1;
	int curres = 0;
	while(l < r){
		if(mp[l] + mp[r] >= lim)++l, -- r,++ curres;
		else ++ l;
	}
	if(curres > curmx)curmx = curres, curid = -1;
	else if(curres == curmx)curid = -1;
	
	// 二分上传哪条边 
	while(le <= ri){
		int mid = le + ri >> 1;
		int l = mid == 0 ? 1 : 0;
		int r = mid == mpc-1 ? mpc-2 : mpc-1;
		int curres = 0;
		while(l < r){
			if(l == mid){
				++l;continue;
			}
			if(r == mid){
				-- r;continue;
			}
			if(mp[l] + mp[r] >= lim)++l, -- r,++ curres;
			else ++ l;
		}
		if(curres > curmx)curmx = curres, curid = mid, le=mid+1;
		else if(curres == curmx)curid = mid, le=mid+1;
		else ri = mid-1;
	}
	
	if(curid == -2){	// 没有配对,直接取最大 
		mx[x] = mp[mp.size() - 1];
		return ;
	}
	if(curid == -1){	// 全配对 
		mx[x] = 0;
		dp[x] += curmx;
		return ;
	}
	mx[x] = mp[curid];	// 部分配对 
	dp[x] += curmx;
}

int check(int lim){
	for(int i=1;i<=n;i++)dp[i] = mx[i] = 0;
	dfs(1, lim);
	if(dp[1] >= m)return 1;
	return 0;
}

signed main(){
//	freopen("P5021_5.in","r",stdin);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n-1;i++){
		int x,y,z;scanf("%d%d%d",&x,&y,&z);
		g[x].push_back(mpr(y,z)), g[y].push_back(mpr(x, z));
	}
	int l = 1, r = 1e9, ans;
	while(l <= r){
		int mid=l+r>>1;
		if(check(mid))l = mid+1, ans = mid;
		else r = mid-1;
	}
	printf("%d\n",ans);

	return 0;
}

Day2
T1
树的60pts很显然,考虑基环树的部分
暴力枚举环上的断边(即从哪条边返回环的起点),变成树即可
注意在环上贪心是不对的,因为环上可能有其它的“叉出去”的边
代码:

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <map>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn=5005;

int n,m;
vector<int>g[maxn];
map<pii,int>ban;

namespace brute{
	int ans[maxn], acnt=0;
	void dfs(int x,int fat = -1){
		ans[++ acnt] = x;
		pair<int,int>pc[maxn];
		for(int u : g[x]){
			if(u == fat)continue;
			dfs(u, x);
		}
	}
};

namespace solve{
	int vis[maxn],pre[maxn],nxt[maxn];
	int dfn[maxn], dfs_clock=0, cyc[maxn], res[maxn];
	int ans[maxn],acnt=0;
	pii bkpoint;
	void dfs(int x,int fat = -1){
		dfn[x] = ++dfs_clock;
//		printf("dfs %d %d\n",x,fat);
		vis[x] = 1;
		for(int u : g[x]){
			if(u == fat)continue;
			if(vis[u]){bkpoint = mpr(x,u);continue;}
			dfs(u, x);
			pre[u] = x;
		}
	}
	int gg = -1;
	void dfs2(int x,int fat = -1){
		for(int u : g[x]){
			if(u == fat)continue;
			if(ban.count(mpr(x,u)))continue;
			ans[++acnt] = u;
			dfs2(u, x);
		}
	}
	void main(){
		memset(res, 0x3f,sizeof res);
		dfs(1);
		if(dfn[bkpoint.first] > dfn[bkpoint.second])swap(bkpoint.first,bkpoint.second);
		int u = bkpoint.first, v = bkpoint.second;
		while(u != v){
			cyc[v] = 1;
			nxt[pre[v]] = v;
			v = pre[v];
		}
		cyc[u] = 1;
		v = bkpoint.second;

		while(u != v){
			acnt=0;
			memset(vis,0,sizeof vis);
			ban.clear();
			ban[mpr(v, pre[v])] = ban[mpr(pre[v], v)] = 1;
			
			ans[++acnt] = 1;
			
			dfs2(1);
			int win = 1;
			for(int i=1;i<=acnt;i++){
				if(res[i] < ans[i]){win = 0;break;}
				if(res[i] > ans[i])break;
			}
			if(win)memcpy(res, ans, sizeof ans);
			v = pre[v];
		}
		for(int i=1;i<=acnt;i++)printf("%d ",res[i]);
	}
};

signed main(){
//	freopen("travel.in","r",stdin);
//	freopen("travel.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int x,y;scanf("%d%d",&x,&y);
		g[x].push_back(y), g[y].push_back(x);
	}
	for(int i=1;i<=n;i++)sort(g[i].begin(),g[i].end());
	if(m == n-1){
		brute::dfs(1);
		for(int i=1;i<=brute::acnt;i++)printf("%d ",brute::ans[i]);puts("");
		return 0;
	}
	solve::main();

	return 0;
}

T2
一眼打表找规律
暴力打表5 5就出不来了,考虑分析一下性质

  1. 对于所有副对角线从上到下填的数一定单调不减
  2. 如果(x-1,y) = (x,y-1),那么矩阵(x,y) -- (n,m)一定都有副对角线填的数都相同(如果不相同,一定可以有两种对称情况,字典序不变,而数字大小正好反过来了)

每次维护一个b[x][y]表示以(x,y)为左上角的矩阵是否满足性质2,可以对列维护一个二进制数,每次判断可以直接 & 一下
跑出来之后发现当 \(m>n+1\)之后\(Ans(n,m)=3 * Ans(n,m-1)\)

打表代码:

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino"<<endl
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,maxn=20005;

int n,m;
int a[105][105],b[105][105];
int g[105][105];

int check(int x,int y){
	a[x][y] = a[x+1][y] | (g[x][y] << (n-x));
	if(y == m)b[x][y] = 1;
	else b[x][y] = b[x][y+1] && (a[x][y+1] >> 1) == a[x+1][y];
	if(x < n && y > 1 && !b[x+1][y] && g[x][y] == g[x+1][y-1])return 0;
	return 1;
}
int ans=0;

void dfs(int x,int y){
	if(y == 0){
		if(x == 1)++ ans;
		else dfs(x-1, m);
		return ;
	}
	if(x == n || y == 1 || g[x+1][y-1] == 1){
		g[x][y] = 1;
		if(check(x,y))dfs(x,y-1);
	}
	g[x][y] = 0;
	if(check(x,y))dfs(x,y-1);
}

signed main(){
	freopen("5023.out","w",stdout);
	for(n=2;n<=9;n++)
	for(m=2;m<=9;m++){
		ans=0;
		memset(a,0,sizeof a),memset(b,0,sizeof b),memset(g,0,sizeof g); 
//	scanf("%d%d",&n,&m);
		dfs(n,m);
		printf("(%d,%d): %d\n",n,m,ans);
	}

	return 0;
}

代码:

#include <cstdio>
#include <iostream>
#define pii pair<int,int>
#define mpr make_pair
using namespace std;
const int mod = 1e9+7;
int pw(int x,int y){
    if(!y)return 1;
    if(y == 1)return x;
    int mid = pw(x, y>>1);
    if(y&1)return 1ll*mid*mid%mod*x%mod;
    return 1ll*mid*mid%mod;
}
int main(){
    int n,m;scanf("%d%d",&n,&m);
    if(n>m){int t=n;n=m;m=t;}
    if(n <= 3 && m <= 3){
        if(n == 2 && m == 2)puts("12");
        else if(n == 3 && m == 3)puts("112");
        else if((n==2&&m==3) || (n==3&&m==2))puts("36");
        else printf("%d\n",pw(2,m));
        return 0;
    }
    if(n==2)printf("%d\n",4ll*pw(3, m-1)%mod);
    else if(n==3)printf("%d\n",112ll*pw(3,m-3)%mod);
    else{
    	pii tb[]={mpr(912,2688),mpr(7136,21312),mpr(56768,170112),mpr(453504,1360128),mpr(3626752,10879488)};
    	if(n == m)printf("%d\n",tb[n-4].first);
    	else if(m == n+1)printf("%d\n",tb[n-4].second);
    	else printf("%d\n",1ll * tb[n-4].second * pw(3,m-n-1) % mod);
	}
    
    return 0;
}
posted @ 2022-09-24 21:42  SkyRainWind  阅读(9)  评论(0编辑  收藏  举报