NOIP2012 复盘

NOIP2012复盘

D1T1 P1079 Vigenère 密码

只要把A到Z换成0到25,那么这个运算就变成了一个膜为26的加法了。

记得不够的时候将\(k\)重复使用即可。

代码:

#include<iostream>
using namespace std;
string key, str;
char cal(char k, char s)
{
	if(k >= 'A' && k <= 'Z') k += 32;
	if(s >= 'A' && s <= 'Z') s += 32;
	char ans = s - k + 'a';
	while(ans < 'a') ans += 26;
	while(ans > 'z') ans -= 26;
	return ans;
}
int main()
{
	cin >> key >> str;
	for(int i = 0; i < str.length(); i++)
	{
		bool big = str[i] >= 'A' && str[i] <= 'Z';
		char temp = cal(key[i % key.length()], str[i]);
		if(big) temp -= 32;
		cout << temp;
	}
	cout << endl;
	return 0;
}

D1T2 P1080 国王游戏

显然不可以遍历所有的排列获得最优解,不过我们可以拿来对拍。

我们考虑一个国王两个大臣的情况。国王左右手为\(a\)\(b\),大臣A左右手为\(a_1\)\(b_1\),大臣B为\(a_2\)\(b_2\)

如果是国王 A B这么排的话,A是\(\frac{a}{b_1}\),B是\(\frac{a\times a_1}{b_2}\)\(ans_1\)是两个的最大值。

如果是国王 B A这么排的话,A是\(\frac{a}{b_2}\),B是\(\frac{a\times a_2}{b_1}\)\(ans_2\)是两个的最大值。

可以知道\(\frac{a}{b_1}\leq \frac{a\times a_2}{b_1}\)\(\frac{a}{b_2} \leq \frac{a \times a_1}{b_2}\)

\(ans_1<ans_2\),则\(\frac{a \times a_1}{b_2}<\frac{a\times a_2}{b_1}\),即\(a_1b_1<a_2b_2\)

发现推回去也是一样的,这是充要条件。

所以当我们把这两个大臣按\(a_ib_i\)从小到大排序时,会得到更小的答案。

按照这个原理,我们可以随机取任何的两个大臣,按交换位置前后去证明,结果是一样的。

所以结论是:\(a_ib_i\)从小到大排序时,能得到最优解。

套一个高精即可

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn = 1005;
struct Nodes
{
	int l, r;
	bool operator < (const Nodes &rhs) const
	{
		return l * r < rhs.l * rhs.r;
	}
} s[maxn];
int n;
int L, R;
struct INT
{
	int a[10005], len;
	INT()
	{
		memset(a, 0, sizeof a);
		len = 0;
	}
	void init(int x)
	{
		if(x == 0) len = 1;
		else
		{
			while(x)
			{
				a[len++] = x % 10;
				x /= 10;
			}
		}
	}
	bool operator < (const INT &rhs) const
	{
		if(len != rhs.len) return len < rhs.len;
		for(int i = len - 1; i >= 0; i--)
		{
			if(a[i] < rhs.a[i]) return true;
			else if(a[i] > rhs.a[i]) return false;
		}
		return false;
	}
	INT operator * (const int &rhs) const
	{
		INT ret;
		for(int i = 0; i < len; i++) ret.a[i] = a[i] * rhs;
		int llen;
		for(int i = 0; i < len || ret.a[i]; i++)
		{
			if(ret.a[i] / 10)
			{
				ret.a[i + 1] += ret.a[i] / 10;
				ret.a[i] %= 10;
			}
			llen = i;
		}
		if(ret.a[llen] == 0) ret.len = llen;
		else ret.len = llen + 1;
		return ret;
	}
	INT operator / (const int &x) const
	{
		INT ret;
		ret.len = len;
		int rest = 0;
		for(int i = len - 1; i >= 0; i--)
		{
			rest = rest * 10 + a[i];
			ret.a[i] = rest / x;
			rest %= x;
		}
		while(ret.len > 1 && ret.a[ret.len - 1] == 0) ret.len--;
		return ret;
	}
	void print()
	{
		for(int i = len - 1; i >= 0; i--) printf("%d", a[i]);
		printf("\n");
	}
};
int main()
{
	/*
	while(233)
	{
		int x, y; scanf("%d%d", &x, &y);
		INT xx; xx.init(x);
		INT yy; yy.init(y);
		INT res1 = xx * y, res2 = xx / y;
		res1.print();
		res2.print();
		printf("%d\n", xx < yy);
	}
	return 0;
	*/
	
	scanf("%d%d%d", &n, &L, &R);
	for(int i = 1; i <= n; i++) scanf("%d%d", &s[i].l, &s[i].r);
	std::sort(s + 1, s + n + 1);
	INT temp; temp.init(L);
	INT ans;
	for(int i = 1; i <= n; i++)
	{
		ans = std::max(ans, temp / s[i].r);
		temp = temp * s[i].l;
	}
	ans.print();
	return 0;
	
}

D1T3 P1081 开车旅行

这道题暴力挺好写的,但是正解的倍增做法很难。

首先应该预处理出小A和小B在每个城市的下一个城市是哪个,也就是去找后面的最小点和次小点。

预处理有两种方法,第一种是建一颗以海拔为关键字的平衡树,依次插入找前驱后继即可。

第二种是离散化之后拿坐标去建双向链表,用很多次判断去找到最小点和次小点。

预处理之后设\(f[i][j]\)为从第\(i\)座城市开始,A和B每人都开了\(2^j\)天所到的点。

\(dpa[i][j]\)\(dpb[i][j]\)为上述路程中A B走的路程。

上面三个数组的递推都很简单,但是初始化不简单,需要注意。

第一问就可以遍历所有的起点城市,总共用\(O(n \log n)\)的复杂度回答。

第二问直接可以通过倍增从大到小慢慢凑,就可以求出路程总数。

代码细节很多,很不好写:

#include<bits/stdc++.h>
const int maxn = 100005;
int n, m, l, r, j;
struct Nodes {
    int val, idx, left, right;
    bool operator < (const Nodes &rhs) const {
        return val < rhs.val;
    }
} d[maxn];
int p[maxn];
int dpa[maxn][21], dpb[maxn][21], f[maxn][21];
int na[maxn], nb[maxn], a, b, ans = n;
double minv = 2147483647;
bool zuo() {
    if(!l) return false;
    if(!r) return true;
    return d[j].val - d[l].val <= d[r].val - d[j].val;
}
int pd(int a, int b) {
    if(!a) return d[b].idx;
    if(!b) return d[a].idx;
    if(d[j].val - d[a].val <= d[b].val - d[j].val) return d[a].idx;
    else return d[b].idx;
}
void init() {
    int i, j;
    for(j = 1; j <= 20; j++) {
        for(int i = 1; i <= n; i++) {
            f[i][j] = f[f[i][j - 1]][j - 1];
            dpa[i][j] = dpa[i][j - 1] + dpa[f[i][j - 1]][j - 1];
            dpb[i][j] = dpb[i][j - 1] + dpb[f[i][j - 1]][j - 1];
        }
    }
}
void solve(int s, long long x) {
    int i, j;
    a = b = 0;
    for(i = 20; i >= 0; i--) {
        if(f[s][i] && 0ll + a + b + dpa[s][i] + dpb[s][i] <= x) {
            a += dpa[s][i]; b += dpb[s][i];
            s = f[s][i];
        }
    }
    if(na[s] && a + b + dpa[s][0] <= x) a += dpa[s][0];
}
int main() {
    int i; long long x;
    scanf("%d", &n);
    for(i = 1; i <= n; i++) scanf("%d", &d[i].val), d[i].idx = i;
    std::sort(d + 1, d + n + 1);
    for(i = 1; i <= n; i++) {
        p[d[i].idx] = i; d[i].left = i - 1; d[i].right = i + 1;
    }
    d[1].left = d[n].right = 0;
    for(i = 1; i <= n; i++) {
        j = p[i]; l = d[j].left; r = d[j].right;
        if(zuo()) nb[i] = d[l].idx, na[i] = pd(d[l].left, r);
        else nb[i] = d[r].idx, na[i] = pd(l, d[r].right);
        if(l) d[l].right = r;
        if(r) d[r].left = l;
    }
    for(i = 1; i <= n; i++) {
        f[i][0] = nb[na[i]];
        dpa[i][0] = abs(d[p[i]].val - d[p[na[i]]].val);
        dpb[i][0] = abs(d[p[f[i][0]]].val - d[p[na[i]]].val);
    }
    init();
    scanf("%lld %d", &x, &m);
    for(i = 1; i <= n; i++) {
        solve(i, x);
        if(b && 1.0 * a / b < minv) {
            minv = 1.0 * a / b;
            ans = i;
        }
    }
    printf("%d\n", ans);
    for(i = 1; i <= m; i++) {
        scanf("%d %lld", &j, &x);
        solve(j, x);
        printf("%d %d\n", a, b);
    }
    return 0;
}

D2T1 P1082 同余方程

其实这就是求逆元的模板题啊!(题目保证有解)

同余方程化成不定方程就是\(ax-1=kb\),也就是\(ax-kb=1\)

exgcd求的是\(ax+by=m\),这是最标准的方程形式。

这个方程有解,当且仅当\(m \mod gcd(a,b)=0\)

而因为此处\(m=1\),所以\(gcd(a,b)=1\),即\(ab\)互质。

所以直接按照最标准的exgcd跑一跑,找到那个正的最小的\(x\)即可。

#include<cstdio>
using namespace std;
int exgcd(int a, int b, int &x, int &y)
{
	if(b == 0)
	{
		x = 1;
		y = 0;
		return a;
	}
	int r = exgcd(b, a %b, x, y);
	int t = x;
	x = y;
	y = t - a / b * y;
	return r;
}
int inverse(int a, int b)
{
	int x, y;
	exgcd(a, b, x, y);
	return (x + b) % b;
}
int main()
{
	int a, b;
	scanf("%d%d", &a, &b);
	printf("%d\n", inverse(a, b));
	return 0;
}

D2T2 P1083 借教室

这道题比D1T2简单多了好吧

我们把这些教室看成一个大区间,那么就变成区间上的操作了。

第一种做法是直接建出线段树,每次借教室直接区间减,当询问到最小值小于0时就停止。

这种做法常数大,远古老爷机一定跑不过的。

第二种是利用题目的单调性进行二分答案,用差分来检验答案。

题目告诉我们一旦有不满足的立即停止,而满足越多越容易不满足。所以就可以二分答案了。

check函数中对每一次借教室只需要前端+1,后端再后的位置-1即可。最后跑一次前缀和就完事了。

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1000005;
int a[maxn], diff[maxn];
struct Node
{
	int u, v, w;
} s[maxn];
int n, m;
int read()
{
	int ans = 0, s = 1;
	char ch = getchar();
	while(ch > '9' || ch < '0')
	{
		if(ch == '-') s = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9')
	{
		ans = ans * 10 + ch - '0';
		ch = getchar();
	}
	return s * ans;
}
bool check(int mid)
{
	memset(diff, 0, sizeof(diff));
	for(int i = 1; i <= mid; i++)
	{
		diff[s[i].u] += s[i].w;
		diff[s[i].v + 1] -= s[i].w;
	}
	for(int i = 1; i <= n; i++)
	{
		diff[i] += diff[i - 1];
		if(diff[i] > a[i]) return false;
	}
	return true;
}
int main()
{
	n = read(), m = read();
	for(int i = 1; i <= n; i++) a[i] = read();
	for(int i = 1; i <= m; i++)
	{
		s[i].w = read(), s[i].u = read(), s[i].v = read();
	}
	if(check(m))
	{
		printf("0\n");
		return 0;
	}
	int left = 1, right = m, ans = 0;
	while(left <= right)
	{
		int mid = (left + right) >> 1;
		if(check(mid)) ans = mid, left = mid + 1;
		else right = mid - 1;
	}
	printf("-1\n%d\n", ans + 1);
	return 0;
}

D2T3 P1084 疫情控制

https://www.luogu.org/blog/qzh/p1084-yi-qing-kong-zhi

#include<bits/stdc++.h>

const int maxn = 100005;
int n, m;
struct Edges {
	int next, to, weight;
} e[maxn];
int head[maxn], tot;
int query[maxn], qtot;
int fa[maxn][21], dist[maxn][21];
int read() {
	int ans = 0, s = 1;
	char ch = getchar();
	while(ch > '9' || ch < '0') {
		if(ch == '-') s = -1;
		ch = getchar();
	}
	while(ch >= '0' && ch <= '9') {
		ans = ans * 10 + ch - '0';
		ch = getchar();
	}
	return s * ans;
}
void link(int u, int v, int w) {
	e[++tot] = (Edges){head[u], v, w};
	head[u] = tot;
}
void dfs1(int u, int f, int dis) {
	fa[u][0] = f; dist[u][0] = dis;
	for(int j = 1; j <= 20; j++) {
		fa[u][j] = fa[fa[u][j - 1]][j - 1];
		dist[u][j] = dist[u][j - 1] + dist[fa[u][j - 1]][j - 1];
	}
	for(int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if(v == f) continue;
		dfs1(v, u, e[i].weight);
	}
}

struct Nodes {
	int val, idx;
	bool operator < (const Nodes &rhs) const {
		return val < rhs.val;
	}
} s[maxn];//¿ÉÒÔÖ§Ô®±ðÈ˵Ä
int stot;
bool vis[maxn];// ÒѾ­×¤ÔúÁËÂð
bool need[maxn];
int a[maxn], atot;
int b[maxn], btot;

bool dfs(int u) {
	bool isleaf = true;
	if(vis[u]) return true;
	for(int i = head[u]; i; i = e[i].next) {
		int v = e[i].to;
		if(v == fa[u][0]) continue;
		isleaf = false;
		if(!dfs(v)) return false;
	}
	if(isleaf) return false;
	return true;
}
bool check(int mid) {
	// init
	memset(vis, false, sizeof vis);
	memset(need, false, sizeof need);
	stot = atot = btot = 0;
	
	for(int i = 1; i <= m; i++) {
		int now = query[i], dis = 0;
		for(int j = 20; j >= 0; j--) {
			if(fa[now][j] > 1 && dis + dist[now][j] <= mid) {
				dis += dist[now][j]; now = fa[now][j];
			}
		}
		if(fa[now][0] == 1 && dis + dist[now][0] <= mid) {
			s[++stot] = (Nodes){mid - dis - dist[now][0], now};
		} else vis[now] = true;
	}
	
	for(int i = head[1]; i; i = e[i].next) {
		int v = e[i].to;
		if(!dfs(v)) need[v] = true;
	}
	
	std::sort(s + 1, s + stot + 1);
	for(int i = 1; i <= stot; i++) {
		if(need[s[i].idx] && s[i].val < dist[s[i].idx][0]) need[s[i].idx] = false;
		else a[++atot] = s[i].val;
	}
	
	for(int i = head[1]; i; i = e[i].next) {
		int v = e[i].to;
		if(need[v]) {
			b[++btot] = dist[v][0];
		}
	}
	if(atot < btot) return false;
	std::sort(a + 1, a + atot + 1);
	std::sort(b + 1, b + btot + 1);
	int i = 1, j = 1;
	while(i <= btot && j <= atot) {
		if(a[j] >= b[i]) i++, j++;
		else j++;
	}
	if(i > btot) return true;
	return false;
}
int main() {
	int left = 0, right = 0, ans = -1;
	n = read();
	for(int i = 1; i < n; i++) {
		int u = read(), v = read(), w = read();
		link(u, v, w); link(v, u, w);
		right += w;
	}
	dfs1(1, 0, 0);
	m = read();
	for(int i = 1; i <= m; i++) query[i] = read();
	while(left <= right) {
		int mid = (left + right) / 2;
		if(check(mid)) ans = mid, right = mid - 1;
		else left = mid + 1;
	}
	printf("%d\n", ans);
	return 0;
}
posted @ 2019-10-03 09:06  Garen-Wang  阅读(173)  评论(0编辑  收藏  举报