CDZSC_2022寒假个人训练赛21级(7)题解

  • 简单
    • C gcd
    • D 模拟
    • E
  • 中等
    • A 01背包
    • I 贪心
  • 困难
    • B 线段树/树状态数组 区间查询+区间修改
    • F dfs+最优性剪枝+状态压缩
    • G 矩阵快速幂
    • H 素数筛法

A Charm Bracelet POJ - 3624

题意

给你N个物品,每个具有Wi重量和Di价值,问你在不超过M的总重量前提下,能获得的最大价值是多少?

题解

01背包裸题

AC代码

int dp[13885];
struct xxx {
	int w, d;
}s[4005];

int main() {
	int n, m,x,y;
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &x, &y);
		s[i].w = x, s[i].d = y;
	}
	for (int i = 1; i <= n; i++) {
		for (int j = m; j >0 ; j--) {
			if (j >= s[i].w)
				dp[j] = max(dp[j], dp[j - s[i].w] + s[i].d);
			else dp[j] = dp[j];
		}
	}
	printf("%d\n", dp[m]);
    return 0;
}

B A Simple Problem with Integers POJ - 3468

题意

给定数组,和一组操作,操作有两种,一种是数组区间加上一个值,一种是求数组区间和。

题解

线段树/树状数组 区间修改,区间查询裸题。

AC代码

//线段树
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstdio>
#include<queue>
#include<cstring>
#include<ctime>
#include<string>
#include<vector>
#include<map>
#include<list>
#include<set>
#include<stack>
#include<bitset>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;

struct tree {
	ll sum, laz;
	ll l, r;
}t[400005];


void pushup(ll p) {
	t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}
void pushdown(ll p) {
	t[p * 2].sum += t[p].laz*(t[p*2].r-t[p*2].l+1);
	t[p * 2 + 1].sum += t[p].laz*(t[p * 2+1].r - t[p * 2+1].l + 1);;
	t[p * 2].laz += t[p].laz;
	t[p * 2 + 1].laz += t[p].laz;
	t[p].laz = 0;
}


void build(ll l, ll r, ll p) {
	t[p].l = l;
	t[p].r = r;
	t[p].sum = 0;
	t[p].laz = 0;
	if (l == r) {
		scanf("%lld", &t[p].sum);
		return;
	}
	ll mid = l + r >> 1;
	build(l, mid, p * 2);
	build(mid+1, r, p * 2+1);
	pushup(p);
}


void update(ll L, ll R,ll k ,ll p) {
	ll l = t[p].l, r = t[p].r;
	if (L <= l && r <= R) {
		t[p].sum += (r - l + 1)*k;
		t[p].laz += k;
		return;
	}
	pushdown(p);
	ll mid = l + r >> 1;
	if (L <= mid)update(L, R, k, p * 2);
	if(R>mid) update(L, R, k, p * 2 + 1);
	pushup(p);
}

ll query(ll L, ll R, ll p) {
	ll l = t[p].l, r = t[p].r;
	if (L <= l && r <= R) {
		return t[p].sum;
	}
	pushdown(p);
	ll mid = l + r >> 1;
	ll ans = 0;
	if (L <= mid)ans += query(L, R, p * 2);
	if (R > mid)ans += query(L, R, p * 2 + 1);
	pushup(p);
	return ans;
}

int main() {
	ll n,m;
	scanf("%lld%lld", &n, &m);
	build(1, n, 1);
	char com;
	ll l, r, k;
	while (m--) {
		scanf(" %c", &com);
		if (com == 'Q') {
			scanf("%lld%lld", &l, &r);
			printf("%lld\n", query(l, r, 1));
		}
		else {
			scanf("%lld%lld%lld", &l, &r, &k);
			update(l, r, k, 1);
		}
	}
}
//树状数组
#include<iostream>
#include<cstring>
#include<cmath>
#include<vector>
#include<algorithm>
#define lowbit(x) (x)&(-x)
using namespace std;
typedef long long ll;
ll tree[100005], c[100005];
void updata(ll i, ll x, ll*tree) {
	while (i <= 100005) {
		tree[i] += x;
		i += lowbit(i);
	}
}

void updata(ll l, ll r, ll x) {
	updata(l, x, tree);
	updata(r + 1, -x, tree);
	updata(l, (l - 1) * x, c);
	updata(r + 1, -r * x, c);
}

ll query(ll x, ll *tree) {
	ll ans = 0;
	while (x > 0) {
		ans += tree[x];
		x -= lowbit(x);
	}
	return ans;
}

ll query(ll x) {
	return x * query(x, tree) - query(x, c);
}
ll query(ll l, ll r) {
	return query(r) - query(l - 1);
}
ll s[100005];

int main() {
	int n, q;
	char com;
	int l, r, x;
	scanf("%d%d", &n,&q);
	for (ll i = 1; i <= n; i++) {
		scanf("%lld", s + i);

	}
	for (ll i = 1; i <= n; i++) {
		updata(i, s[i] - s[i - 1], tree);
		updata(i, (i-1)*(s[i] - s[i - 1]), c);
	}
	while (q--) {
		scanf(" %c", &com);
		if (com == 'Q') {
			scanf("%d%d", &l, &r);
			printf("%lld\n",query(l, r));
		}
		else if (com == 'C') {
			scanf("%d%d%d", &l, &r,&x);
			updata(l, r, x);
		}
	}

}

C Visible Lattice Points POJ - 3090

题意

输入n,问从坐标(0,0)点能看到多少点,点的坐标范围是0<=(x,y)<=n
只有一个点没有被另一个点挡住才能看到。

题解

枚举gcd判断是否被遮挡,如果两个数gcd不为1,说明,有比它更小的同比例的点,会遮挡它,多个查询,打表后输出就好。

AC代码

#include<iostream>

using namespace std;

int gcd(int a, int b) {
	return b == 0 ? a : gcd(b, a%b);
}
const int max_n = 1000;
int b[max_n+5];

int main() {
	int T, n;
	scanf("%d", &T);
	int ans = -1;
	for (int i = 1; i <= max_n; i++) {
		
		for (int j = 0; j <= i; j++) {
			if (gcd(i, j) == 1)ans++;
			if (gcd(j, i) == 1)ans++;
		}
		b[i] = ans;
	}


	for (int t = 1; t <= T; t++) {
		scanf("%d", &n);
		printf("%d %d %d\n",t,n,b[n]);
	}


	return 0;
}

D Coder CodeForces - 384A

题意

给出n,在一个n * n的棋盘,要求在这个棋盘上方尽量多的棋子,棋子不能相邻,给出摆法。

题解

简单模拟,交替输出.和C就好

AC代码

int main() {
	int n;
	scanf("%d", &n);
	printf("%d\n", (n*n+1)/2);
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			putchar((i+j&1)?'.':'C');
		}
		putchar('\n');
	}

}

E Minimum Binary Number CodeForces - 976A

题意

给一个长为n的序列,每次都可以做两种操作之一:

  1. 把一对0和1的位置互换;
  2. 把11变成1
    输出一个可以获得的最短序列。
    (如果最短序列中有1和0,1一定先于0出现,例如100而不是001)

题解

所有的1都可以通过1、2操作变成一个1,然后把剩下0输出就好

AC代码

int main() {
	int n;
	int a = 0, b = 0,x;
	scanf("%d", &n);
	for (int i = 0; i < n; i++) {
		scanf("%1d", &x);
		if (x)a++;
		else b++;
	}
	if (a)printf("1");
	while (b--)printf("0");
	printf("\n");
}

F 海贼王之伟大航路 OpenJ_Bailian - 4124

题解

dfs+最优性剪枝+状态压缩
两个剪枝

  1. 目前的路程超过以及求出的一个可达路程,那直接可以剪掉。

    if (sum >= ans)return; 
    
  2. 保存所有状态中最优的那个路径,如果出现了一种状态的新路径并且路径更短那么更新它,如果更长则剪掉。 状态使用二进制和一个编号来表示,比如状态sta[0101][b],表示目前以及经过了第1个和第3个岛屿(0101=\(1^1+2^0+4^1+8^0\),在第一个位置上是1,第三个位置也是1,所以表示以及经过1,3岛屿这就是状态压缩,或者说二进制压缩),目前在第b个岛屿。这样子的表示能把所有状态划分。

    if (sta[tmp][x] > sum) {
    			sta[tmp][x] = sum;
    			dfs(i, k + 1);
    		}
    

AC代码

int s[20][20];
int vis[20];
int n, ans = 1e9;
int sta[100005][20];
int tmp = 0;
int sum = 0;
void dfs(int x, int k) {
	if (x == n) {
		if (k == n) {
			ans = min(ans, sum);
		}
		return;
	}
	if (sum >= ans)return;


	for (int i = 2; i <= n; i++) {
		if (vis[i] == 0) {
			vis[i] = 1;
			tmp += 1 << (i - 2);
			sum += s[x][i];

			if (sta[tmp][x] > sum) {
				sta[tmp][x] = sum;
				dfs(i, k + 1);
			}
			
			sum -= s[x][i];
			tmp -= 1 << (i - 2);
			vis[i] = 0;
		}
	}

}

int main() {

	while (~scanf("%d", &n)) {
		memset(vis, 0, sizeof vis);
		memset(sta, 0x3f, sizeof sta);
		ans = 1e9;
		tmp = 0;
		sum = 0;
		for (int i = 1; i <= n; i++) {
			for (int j = 1; j <= n; j++) {
				scanf("%d", &s[i][j]);
			}
		}
		dfs(1, 1);
		printf("%d\n", ans);
	}

	return 0;

}

G Fibonacci POJ - 3070

题意

菲波那契数列是指这样的数列: 数列的第一个是0和第二个数是1,接下来每个数都等于前面2个数之和。 给出一个正整数a,要求菲波那契数列中第a个数的后四位是多少。

题解

矩阵快速幂,
具体可以看我的另一篇博客从斐波那契到矩阵快速幂

AC代码

#include<iostream>

using namespace std;

int n,b[100005];

typedef long long ll;
const ll mod = 10000;
const int N = 2;//矩阵大小
int c[N][N];
//状态转移
void mul(ll f[N], ll a[N][N]) {
    ll c[N];
    memset(c, 0, sizeof(c));
    for (int j = 0; j < N; j++)
        for (int k = 0; k < N; k++)
            c[j] = (c[j] + f[k] * a[k][j]) % mod;
    memcpy(f, c, sizeof(c));
}
//自乘
void mulself(ll a[N][N]) {
    ll c[N][N];
    memset(c, 0, sizeof(c));
    for (int i = 0; i < N; i++)
        for (int j = 0; j < N; j++)
            for (int k = 0; k < N; k++)
                c[i][j] = (c[i][j] + (a[i][k] * a[k][j])) % mod;
    memcpy(a, c, sizeof(c));
}

//f是状态矩阵,a是转移矩阵,n是指数
ll* pow(ll f[N], ll a[N][N],ll n) {

    while (n) {
        if (n & 1)mul(f, a);
        mulself(a);
        n >>= 1;
    }
    return f;
}

int main(){
	int x;

	while(scanf("%d",&x),x!=-1){
		ll a[][2]={
			{0,1},
			{1,1}
		};
		ll f[]={0,1};
		printf("%d\n",*pow(f,a,x));
	}


	


}

H Prime Distance POJ - 2689

题意

求给定区间内的质数距离最小的一对和质数距离最大的一对。

题解

先筛出\(\sqrt{2147483647}\) 以内的素数,然后用这些素数就可以筛出范围区间素数。
实际上 \(N\) 以内的合数都是由 \(\sqrt{N}\) 以内的数构成的,很容易证明,因为如果你有个合数是由\(sqrt{N}\)以外的素数组成,我们假如组成这个合数的两个素数是 \(a=(\sqrt{N}+a),b=(\sqrt{N}+b)\),那\(a*b=(\sqrt{N}+a)*(\sqrt{N}+b)\)肯定是大于\(N\)的。
第二次筛法,和第一次差不多,把在范围内的已经算出的素数的倍数,筛去,剩下的就是素数了。

AC代码

#include<iostream>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
ll vis[1000006];
ll p[100005];

ll e(ll n) {
	for (ll i = 2; i*i <= n; i++)
		if (!vis[i])
			for (ll j = i * i; j <= n; j += i)
				vis[j] = 1;
	ll k = 0;
	for (ll i = 2; i <= n; i++)
		if (!vis[i])
			p[k++] = i;
	return k;
}

ll p2[1000005];
ll vis2[1000005];
void pab(ll a, ll b, ll k) {
	ll i, j, x;
	memset(vis2, 0, sizeof vis2);

	for (i = 0; i < k; i++) {
		x = a / p[i];
		if (x <= 1)x = 2;
		for (j = p[i] * x; j <= b; j += p[i]) {
			if (j >= a)
				vis2[j - a] = 1;

		}

	}
	if (a == 1)
		vis2[0] = 1;


}

int main() {
	ll k = e(100000);

	ll l, r, mx, mn, mxa, mxb, mna, mnb, c;
	while (~scanf("%lld%lld", &l, &r)) {
		mx = -INF;
		mn = INF;
		pab(l, r, k);
		ll n = 0;
		for (ll i = 0; i <= r - l; i++)
			if (vis2[i] == 0) {
				p2[n++] = i + l;
			}
		if (n <= 1)printf("There are no adjacent primes.\n");
		else {

			for (ll i = 0; i < n - 1; i++) {
				if (p2[i + 1] - p2[i] > mx) {
					mx = p2[i + 1] - p2[i];
					mxa = p2[i];
					mxb = p2[i + 1];
				}
				if (p2[i + 1] - p2[i] < mn) {
					mn = p2[i + 1] - p2[i];
					mna = p2[i];
					mnb = p2[i + 1];
				}

			}
			printf("%lld,%lld are closest, %lld,%lld are most distant.\n", mna, mnb, mxa, mxb);
		}
	}


}

I Intercepted Message CodeForces - 950B

题意

给定两个长度分别为 \(n,m\) 的序列 \(x_1,x_2,⋯,x_n\)\(y_1,y_2,⋯,y_m\),要将序列 \(X\) 和序列 \(Y\) 分别划分成若干段,使得序列 \(X\) 的第 \(i\) 段数字的和等于序列 \(Y\) 的第 \(i\) 段数字的和,问最多能够分成多少段。

题解

贪心
整两个和\(sum_1,sum_2\),从分别两个数组的开头慢慢加,哪个小先加哪个,相同了就将答案加一。

AC代码

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int a[100005], b[100005];

int main() {
	int n, m;
	scanf("%d%d", &n, & m);
	for (int i = 0; i < n; i++)
		scanf("%d", a + i);
	for (int i = 0; i < m; i++)
		scanf("%d", b + i);
	int sum1 = a[0], sum2 = b[0],x=0,y=0;
	int ans =1;
	while (1) {
		if (x >= n-1 && y >= m-1)break;
		else if (sum1 == sum2) {
			ans++;
			x++, y++;
			sum1 += a[x];
			sum2 += b[y];

		}
		else if (sum1 > sum2) {
			y++;
			sum2 += b[y];
		}
		else {
			x++;
			sum1 += a[x];
		}
	}
	
	printf("%d\n", ans);
		
	

	return 0;
}
posted @ 2022-02-10 17:06  _comet  阅读(183)  评论(0编辑  收藏  举报