NOIP2015 解题报告

50 篇文章 0 订阅
44 篇文章 0 订阅

去年十月拿了省一真是挺开心的。。。。


总算是订正得差不多了。。。(斗地主一题实在受不了!!!)


D1T1 神奇的幻方

裸模拟就不说了。。

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn = 50;

int a[maxn][maxn],i,j,n,r,c;

int main()
{
	
	cin >> n;
	int mr = n / 2 + 1;
	int mc = mr;
	a[1][mc] = 1;
	r = 1; c = mc;
	for (i = 2; i <= n*n; i++) {
		if (r == 1 && c != n) {
			a[n][c+1] = i;
			r = n; c = c+1;
			continue;
		}
		if (r != 1 && c == n) {
			a[r-1][1] = i;
			r = r-1; c = 1;
			continue;
		}
		if (r == 1 && c == n) {
			a[r+1][c] = i;
			r = r+1;
			continue;
		}
		if (!a[r-1][c+1]) {
			a[r-1][c+1] = i;
			r = r-1; c = c+1;
		}
		else {
			a[r+1][c] = i;
			r = r+1; 
		}
	}
	for (i = 1; i <= n; i++) {
		for (j = 1; j < n; j++) printf("%d ",a[i][j]);
		printf("%d\n",a[i][j]);
	}
	return 0;
}


D1T2 信息传递

显然找到图中最大环即可

苟蒻用tarjan算法。。不过还好栈有8MB。。比赛的时候完全没考虑过爆栈

#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
using namespace std;

const int maxn = 2e5 + 10;

int sccno[maxn],pre[maxn],low[maxn],Size[maxn],To[maxn],n,i,j,ans = 1E9,d_t = 0,cur = 0;

stack <int> s;

void dfs(int k)
{
	pre[k] = low[k] = ++d_t;
	s.push(k);
	if (!pre[To[k]]) {
		dfs(To[k]);
		low[k] = min(low[k],low[To[k]]);
	}
	else {
		if (!sccno[To[k]]) low[k] = min(low[k],low[To[k]]);
	}
	if (pre[k] == low[k]) {
		int S = 0;  ++cur;
		while (1) {
			int now = s.top(); 
			sccno[now] = cur;
			++S; s.pop();
			if (now == k) break;
		}
		Size[cur] = S;
	}
}

int main()
{
	cin >> n;
	for (i = 1; i <= n; i++) scanf("%d",&To[i]);
	memset(pre,0,sizeof(pre));
	for (i = 1; i <= n; i++)
		if (!pre[i]) dfs(i);
	for (i = 1; i <= n; i++) 
		if (Size[sccno[i]] != 1) ans = min(ans,Size[sccno[i]]);
	cout << ans;
	return 0;
}


D1T3 斗地主

苟蒻终于在2016.3.12A了这道狗日的深搜

提炼一道正解的观点

除了三种龙以外,其他牌同牌点顺序无关,见qwork函数直接算出

剩下就是枚举龙了

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

const int maxn = 15;

int n,t,ans,b[4],a[maxn];

int qwork()
{
	int c1,c2,c3,c4,cnt;
	c1 = c2 = c3 = c4 = cnt = 0;
	for (int i = 0; i < 14; i++) {
		if (a[i] == 1) ++c1;
		if (a[i] == 2) ++c2;
		if (a[i] == 3) ++c3;
		if (a[i] == 4) ++c4;
	}
	while (c4 && c2 > 1) --c4,c2-=2,++cnt;
	while (c4 && c1 > 1) --c4,c1-=2,++cnt;
	while (c3 && c2) --c3,--c2,++cnt;
	while (c3 && c1) --c3,--c1,++cnt;
	while (c4 && c2) --c4,--c2,++cnt;
	return cnt+c1+c2+c3+c4;
} 

void dfs(int now,int rest)
{
	if (now >= ans) return;
	int ret = qwork(); ans = min(ans,now+ret);
	for (int i = 0; i <= 7; i++) if (a[i]) {
		int j = i;
		while (a[j]) ++j;
		if (j - i >= 5) {
			--j;
			j = min(j,11);
			for (int l = j; l >= i+4; l--) {
				for (int k = i; k <= l; k++) --a[k];
				dfs(now+1,rest-l+i-1);
				for (int k = i; k <= l; k++) ++a[k];
			}
		}
	}
	
	for (int i = 0; i <= 9; i++) if (a[i] >= 2) {
		int j = i;
		while (a[j] >= 2) ++j;
		if (j - i >= 3) {
			--j;
			j = min(j,11);
			for (int l = j; l >= i+2; l--) {
				for (int k = i; k <= l; k++) a[k] -= 2;
				dfs(now+1,rest-2*(l-i+1));
				for (int k = i; k <= l; k++) a[k] += 2;
			}
		}
	}
	
	for (int i = 0; i <= 10; i++) if (a[i] >= 3) {
		int j = i;
		while (a[j] >= 3) ++j;
		if (j - i >= 2) {
			--j;
			j = min(j,11);
			for (int l = j; l >= i+1; l--) {
				for (int k = i; k <= l; k++) a[k] -= 3;
				dfs(now+1,rest-3*(l-i+1));
				for (int k = i; k <= l; k++) a[k] += 3;
			}
		}
	}
}

void READ()
{
	memset(a,0,sizeof(a));
	for (int i = 1; i <= n; i++) {
		int x,y; scanf("%d%d",&x,&y);
		if (x > 2) x -= 3;
		else if (!x) x = 13;
		else x += 10;
		++a[x];
	}
}

int main()
{
	#ifdef YZY
		freopen("yzy.txt","r",stdin);
	#endif
	
	cin >> t >> n;
	while (t--) {
		READ();
		ans = n;
		dfs(0,n);
		printf("%d\n",ans);
	}
	return 0;
}



D2T1 跳石头

简单的二分,比赛的时候竟然忘了特判最后一枚石头(即终点)

还好那只有一个点。。。

#include<iostream>
#include<cstdio>
using namespace std;

const int maxn = 5e4 + 50;

int s[maxn],l,m,n,i,j;

bool Judge(int now)
{
	int rig = 0;
	int sum = 0;
	for (i = 1; i <= n; i++) {
		if (s[i] - rig < now) ++sum;
		else rig = s[i];
	}
	if (l - rig < now) ++sum;
	return sum <= m;
}

int main()
{
	
	cin >> l >> n >> m;
	for (i = 1; i <= n; i++) scanf("%d",&s[i]);
	int L = 0,R = l+10;
	while (R - L > 1) {
		int mid = (R+L) >> 1;
		if (Judge(mid)) L = mid;
		else R = mid;
	}
	if (Judge(R)) cout << R;
	else cout << L;
	return 0;
}


D2T2 子串

dp。。。

然而苟蒻太水现场不会做只骗了40分

f[k][i][j]:第一个串到i位置第二个串到j位置,且用第一个串的i字符成为第二个串的j字符,已选用k段的方案数

那么方案转移就变为延续上一种方案的尾部或开一段新的子串

详见代码。。


#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;

typedef long long LL;
const LL mo = 1000000007;

LL f[2][1010][210],sum[2][1010][210];
char s1[1010],s2[210];
int n,m,k,i,j;

int main()
{
	
	cin >> n >> m >> k;
	scanf("%s",1+s1);
	scanf("%s",1+s2);
	f[0][0][0] = 1;
	for (i = 0; i <= n; i++) sum[0][i][0] = 1;
	for (int l = 1; l <= k; l++) {
		memset(f[l&1],0,sizeof(f[l&1]));
		memset(sum[l&1],0,sizeof(sum[l&1]));
		for (i = 1; i <= n; i++)
			for (j = 1; j <= m; j++) {
				if (s1[i] == s2[j]) {
					f[l&1][i][j] = sum[(l-1)&1][i-1][j-1];
					if (s1[i-1] == s2[j-1])
						f[l&1][i][j] = (f[l&1][i][j]+f[l&1][i-1][j-1]) % mo;
				}
				sum[l&1][i][j] = (f[l&1][i][j] + sum[l&1][i-1][j]) % mo;
			}
	}
	
	cout << sum[k&1][n][m];
	return 0;
}

D2T3

比赛强行朴素算法35分。。


据说只有O(n)的神算法才能AC?

苟蒻学了O(nlogn)的使用树上前缀和的算法

显然每个运输方案对应树上1或2条链

用倍增算法求出每个方案初始耗时,经过的路径

二分答案,用树上前缀和求之

(bzoj上竟然可以过)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
using namespace std;

const int maxn = 3E5 + 10;
typedef int LL;

struct E{
	LL to,w,num;
};

struct Q{
	LL x,y,sum,fa;
}q[maxn];

LL n,m,i,j,du[maxn],pass[maxn];
LL anc[maxn][20],sum[maxn][20],t[maxn],vis[maxn],L[maxn],Fa[maxn],Mark[maxn];

vector <E> v[maxn];
vector <LL> Last; 

queue <LL> qu;
queue <LL> qu2;

void Bfs(int x)
{
	qu2.push(x);
	while (!qu2.empty()) {
		bool flag = 0;
		LL k = qu2.front(); qu2.pop();
		for (int l = 0; l < v[k].size(); l++) {
			int to = v[k][l].to;
			if (L[to]) continue;
			flag = 1;
			L[to] = L[k] + 1;
			anc[to][0] = k;
			sum[to][0] = v[k][l].w;
			Fa[to] = v[k][l].num;
			++du[k];
			qu2.push(to);
		}
		if (!flag) Last.push_back(k);
	}
}

void LCA_pre()
{
	for (int l = 1; l < 20; l++)
		for (i = 1; i <= n; i++) {
			anc[i][l] = anc[anc[i][l-1]][l-1];
			sum[i][l] = sum[anc[i][l-1]][l-1] + sum[i][l-1];
		}
}

void solve()
{
	LL x = q[i].x; LL y = q[i].y;
	LL &S = q[i].sum; LL &fa = q[i].fa;
	if (L[x] < L[y]) swap(x,y);
	LL log;
	for (log = 0; L[x] - (1<<log) > 0; ++log);
	
	for (j = log; j >= 0; j--)
		if (L[x] - (1<<j) >= L[y]) {
			S += sum[x][j];
			x = anc[x][j];
		}
	if (x == y) {
		fa = y;
		return;
	}
	
	for (j = log; j >= 0; j--)
		if (anc[x][j] != anc[y][j]) {
			S += sum[x][j] + sum[y][j];
			x = anc[x][j]; y = anc[y][j];
		}
	S += sum[x][0] + sum[y][0];
	fa = anc[x][0];
}

bool Judge(int now)
{
	memset(vis,0,sizeof(vis));
	memset(Mark,0,sizeof(Mark));
	LL tot = 0,Max = 0;
	for (i = 1; i <= m; i++)
		if (q[i].sum > now) {
			++Mark[q[i].x]; ++Mark[q[i].y];
			Mark[q[i].fa] -= 2;
			++tot;
			Max = max(Max,q[i].sum);
		} 
		
	memset(pass,0,sizeof(pass));
	for (int l = 0; l < Last.size(); l++) qu.push(Last[l]);
	
	while (!qu.empty()) {
		LL k = qu.front(); qu.pop();
		vis[Fa[k]] += Mark[k];
		Mark[anc[k][0]] += Mark[k];
		k = anc[k][0];
		++pass[k];
		if (pass[k] == du[k]) qu.push(k);
	}
	
	LL tmp = 0;
	for (i = 1; i < n; i++)
		if (vis[i] == tot) 
			tmp = max(tmp,t[i]);
	
	return Max - tmp <= now;
}

int main()
{
	cin >> n >> m;
	memset(anc,-1,sizeof(anc));
	for (i = 1; i < n; i++) {
		LL x,y,w;
		scanf("%d%d%d",&x,&y,&w);
		v[x].push_back((E){y,w,i});
		v[y].push_back((E){x,w,i});
		t[i] = w;
	}
	L[1] = 1; Bfs(1);
	LCA_pre();
	
	for (i = 1; i <= m; i++) {
		scanf("%d%d",&q[i].x,&q[i].y);
		solve();
	}
	
	int l = -1,r = 3E8 + 30;
	while (r - l > 1) {
		LL mid = (l+r) >> 1;
		if (Judge(mid)) r = mid;
		else l = mid;
	}
	if (Judge(l)) cout << l;
	else cout << r;
	
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值