- 简单
- 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的序列,每次都可以做两种操作之一:
- 把一对0和1的位置互换;
- 把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+最优性剪枝+状态压缩
两个剪枝
-
目前的路程超过以及求出的一个可达路程,那直接可以剪掉。
if (sum >= ans)return;
-
保存所有状态中最优的那个路径,如果出现了一种状态的新路径并且路径更短那么更新它,如果更长则剪掉。 状态使用二进制和一个编号来表示,比如状态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;
}