高考集训Day3
A. Watching Fireworks is Fun
题目描述
一个节日将在一个镇的主街道上举行。 街道被分为n段,从左到右编号为1到n,每个相邻部分之间的距离为1。
节日里在主街道有m个烟花要放,第i(1≤i≤m)次烟花将在ti时在ai段燃放。 如果您在第i次发射时处于x(1≤x≤n),您将获得幸福值bi - | ai - x | (请注意,幸福价值可能是负值)
您可以以单位时间间隔移动长度为d个长度单位,但禁止离开主要街道。 此外,在初始时刻(时间等于1)你可以在街道的任意部分,并希望最大化从观看烟花获得的幸福总和。 找到最大的总幸福。
请注意,两个或多个烟花可以同时发射
输入格式
第一行包含三个整数n, m, d ()
接下来m行每行包含三个整数ai, bi, ti ()。第 i 行描述第 i 次烟花发射
确保满足条件ti≤ti + 1(1≤i<m)
输出格式
输出一个整数,表示观看所有烟花获得的最大幸福总和
样例
样例输入
样例输入1:
50 3 1
49 1 1
26 1 4
6 1 10
样例输入2:
10 2 1
1 1000 4
9 1000 4
样例输出
样例输出1: -31 样例输出2: 1992
CODE
如果用正常版的滚动数组当然也可以,需要注意控制数组的变量一定完全是全局变量,就是不能for(int i=1,w=0;...(w控制数组)
/* emmm...又是一道不会改的题 经过Day2和Day3的考试,我发现我根本就没有真正地学会 怎么用单调队列或斜率优化dp 每用每错 抄起来倒是简单呵呵 ---code is from WintersRain %%% */ #include <bits/stdc++.h> //#define int long long using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const int mod = 998244353; const ll INF = 1152921504606846976;//这个数不错,我要了! int n, m, d, que[maxn]; ll ans, tmp[maxn], dp[maxn]; struct node { int a, b, t; }fire[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } //啊!!!我一定要搞定单调队列,不信这个邪了。。 //只能说看懂了,但我能不能自己写的出来还是个问题 signed main() { freopen("fire.in", "r", stdin); freopen("fire.out", "w", stdout); n = read(); m = read(); d = read(); for(int i=1; i<=m; i++) { fire[i].a = read(); fire[i].b = read(); fire[i].t = read(); } for(int i=1; i<=m; i++) { memcpy(tmp, dp, sizeof(dp));//我第一次看见这么赋值的 //tmp和dp一起组成滚动数组,tmp是上一个烟花 //滚动之前dp[i][j]表示在位置i烟花j的时候获得的最优的欢乐值 //Dp[i][j]=Dp[x][j-1] (x 和 i 之间的差距要在时间内走的到才能转移)+ 当前时刻能够获得的价值 sum; if(fire[i].t == fire[i-1].t)//为什么它可以以防万一??这个式子特殊在哪儿了 { for(int j=1; j<=n; j++)//留在原地 { dp[j] = tmp[j] + fire[i].b - abs(fire[i].a - j); } continue; } int l = 1, r = 0; int k = 1;//k表示上一个烟花燃放时在哪个位置 ll dis = (ll)(fire[i].t - fire[i-1].t) * d;//在这个距离内都可以,没说单位时间只能走d步 //所以说我一开始连题意都没看懂 //dis表示在两次烟花之间可以跑多远 for(int j=1; j<=n; j++)//i燃放时位置为j { while(k <= min(dis+j, (ll)n))//。。不用判断下界是因为下界被删了 { while(l <= r && tmp[k] >= tmp[que[r]]) r--;//单调递减的队列,队首是区间最大值 que[++r] = k; k++; } while(l <= r && j-dis>que[l]) l++; dp[j] = tmp[que[l]] + fire[i].b - abs(fire[i].a - j); } } ans = -INF; for(int i=1; i<=n; i++) { ans = max(ans, dp[i]); } printf("%lld", ans); return 0; }
B. Perform巡回演出
题目描述
Flute 市的 Phlharmoniker 乐团 2000 年准备到 Harp 市做一次大型演出 , 本着普及古典音乐的目的 , 乐团指挥 L.Y.M 准备在到达 Harp 市之前先在周围一些小城市作一段时间的巡回演出 , 此后的几天里 , 音乐家们将每天搭乘一个航班从一个城市飞到另一个城市 , 最后才到达目的地 Harp 市 ( 乐团可多次在同一城市演出 ). 由于航线的费用和班次每天都在变 , 城市和城市之间都有一份循环的航班表 , 每一时间 , 每一方向 , 航班表循环的周期都可能不同 . 现要求寻找一张花费费用最小的演出表 .
输入格式
输入文件包括若干个场景 . 每个场景的描述由一对整数 n(2<=n<=10) 和 k(1<=k<=1000) 开始 , 音乐家们要在这 n 个城市作巡回演出 , 城市用 1..n 标号 , 其中 1 是起点 Flute 市 ,n 是终点 Harp 市 , 接下来有 n*(n-1) 份航班表 , 一份航班表一行 , 描述每对城市之间的航线和价格 , 第一组 n-1 份航班表对应从城市 1 到其他城市 (2,3,...n) 的航班 , 接下的 n-1 行是从城市 2 到其他城市 (1,3,4...n) 的航班 , 如此下去 . 每份航班又一个整数 d(1<=d<=30) 开始 , 表示航班表循环的周期 , 接下来的 d 个非负整数表示 1,2...d 天对应的两个城市的航班的价格 , 价格为零表示那天两个城市之间没有航班 . 例如 "3 75 0 80" 表示第一天机票价格是 75KOI, 第二天没有航班 , 第三天的机票是 80KOI, 然后循环 : 第四天又是 75KOI, 第五天没有航班 , 如此循环 . 输入文件由 n=k=0 的场景结束 .
输出格式
对每个场景如果乐团可能从城市 1 出发 , 每天都要飞往另一个城市 , 最后 ( 经过 k 天 ) 抵达城市 n, 则输出这 k 个航班价格之和的最小值 . 如果不可能存在这样的巡回演出路线 , 输出 0.
样例
样例输入
3 6
2 130 150
3 75 0 80
7 120 110 0 100 110 120 0
4 60 70 60 50
3 0 135 140
2 70 80
2 3
2 0 70
1 80
0 0
样例输出
460 0
CODE
少有的1A
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e5 + 3; const int mod = 998244353; const ll INF = 0x7fffffff; int n, k, t[12][12]; ll f[1002][12]; struct node { int day[31]; }c[12][12]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { freopen("perform.in", "r", stdin); freopen("perform.out", "w", stdout); while(1) { n = read(); k = read(); if(n == 0 && k == 0) break; memset(c, 0, sizeof(c)); memset(t, 0, sizeof(t)); for(int i=1; i<=n; i++) { for(int j=1; j<=n; j++) { if(j == i) continue; t[i][j] = read();//从i到j的周期 for(int k=1; k<=t[i][j]; k++) { c[i][j].day[k] = read();//从i到j第k天的票价 //printf("c[%d][%d].day[%d] == %d\n", i, j, k, c[i][j].day[k]); } } } memset(f, 0x3f, sizeof(f)); f[1][1] = 0; //printf("n==%d k==%d\n", n, k); for(int i=2; i<=k+1; i++) { for(int j=1; j<=n; j++) { for(int kk=1; kk<=n; kk++) { if(kk == j) continue; //printf("i=%d j=%d k=%d\n", i, j, kk); //printf("%d %d\n", i-1, t[kk][j]+1); //int s = (i-1) % (t[kk][j]+1); int s = i-1; while(s > t[kk][j]) { s -= t[kk][j]; } //printf("s == %d\n", s); if(c[kk][j].day[s] == 0) continue; //printf("cmp: %lld %lld\n", f[i][j], f[i-1][kk]+c[kk][j].day[s]); //printf("INSERT: f[%d][%d] == %lld\n", i-1, kk, f[i-1][kk]); f[i][j] = min(f[i][j], f[i-1][kk]+c[kk][j].day[s]); //printf("f[%d][%d] == %lld\n", i, j, f[i][j]); } } } if(f[k+1][n] == 4557430888798830399) { printf("0"); continue; } printf("%lld\n", f[k+1][n]); } return 0; }
C. 枪战Maf
题目描述
有 n个人,用 1~n 进行编号,每个人手里有一把手枪。一开始所有人都选定一个人瞄准(有可能瞄准自己)。然后他们按某个顺序开枪,且任意时刻只有一个人开枪。因此,对于不同的开枪顺序,最后死的人也不同。
输入格式
- 输入人数 n < 1e6
- 接下来 n个数,依次为每个人的 aim
输出格式
输出只有一行,共两个数,为要求最后死亡数目的最小和最大可能。
样例
样例输入
8
2 3 2 2 6 7 8 5
样例输出
3 5
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const int mod = 998244353; const ll INF = 0x7fffffff; int n, dfn[maxn], low[maxn], num, t, belong[maxn]; int q[maxn], cnt, ans1, ans2, du[maxn]; bool v[maxn], zi[maxn], zih[maxn]; stack<int> stk; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } struct node { int next, to; }a[maxn]; int len, head[maxn]; void add(int x, int y) { a[++len].to = y; a[len].next = head[x]; head[x] = len; } struct node2 { int from, to; }b[maxn]; void tarjan(int x) { dfn[x] = low[x] = ++num; stk.push(x); v[x] = 1; for(int i=head[x]; i; i=a[i].next) { int to = a[i].to; if(!dfn[to]) { tarjan(to); low[x] = min(low[x], low[to]); } else if(v[to]) { low[x] = min(low[x], dfn[to]); } } if(low[x] == dfn[x]) { int y; ++t; do { y = stk.top(); stk.pop(); v[y] = 0; belong[y] = t; if(zi[y]) zih[t] = 1; //printf("belong[%d] == %d\n", y, belong[y]); q[t]++; }while(y != x); } } int main() { freopen("maf.in", "r", stdin); freopen("maf.out", "w", stdout); n = read(); for(int i=1; i<=n; i++) { int x = read(); add(i, x); b[i].from = i; b[i].to = x; if(i == x) zi[i] = 1; } for(int i=1; i<=n; i++) { if(!dfn[i]) { tarjan(i); } } for(int i=1; i<=n; i++) { if(belong[b[i].to] != belong[b[i].from]) { du[belong[b[i].to]]++; } } //printf("t == %d\n", t); for(int i=1; i<=t; i++) { //printf("du[%d] == %d\n", i, du[i]); if(du[i] > 0) { ans1++; ans2++; } } for(int i=1; i<=t; i++) { if(q[i] == 1) continue; ans1 += (q[i]>>1); ans2 += q[i] - 1; //printf("ans1 == %d ans2 == %d\n", ans1, ans2); if(du[i] > 0) { ans1--; } } for(int i=1; i<=t; i++) { if(zih[i] && (!du[i])) { ans1++; ans2++; } } printf("%d %d", ans1, ans2); return 0; }
/* 我知道我那个tarjan的为什么错了: 死的人最多能找对,但是最少的情况我把缩点之后入度不为零的都算进去了 尽管有关环的处理好像和正解一样 然后,开启借鉴模式 code is from:WintersRain 我似乎明白了 在万能头文件下kill是关键字 */ #include <cstdio> #include <cstring> #include <string> using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const int mod = 998244353; const ll INF = 0x7fffffff; int n, aim[maxn], lve, sze, pos, que[maxn], ipt[maxn]; bool kill[maxn], exist[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { freopen("maf.in", "r", stdin); freopen("maf.out", "w", stdout); n = read(); for(int i=1; i<=n; i++) { aim[i] = read(); ipt[aim[i]]++; } for(int i=1; i<=n; i++) { if(ipt[i] == 0) { lve++; que[++sze] = i; } } pos = 1; while(pos <= sze)//que队列维护入度是0的点,入度是0一定能活,那么他的目标一定会死 { int frnt = que[pos]; pos++; if(kill[aim[frnt]]) continue; kill[aim[frnt]] = true; int targ = aim[aim[frnt]]; ipt[targ]--; exist[targ] = true;//追求杀的人最多targ一定会死 //in fact 追求杀的人最多的话有入度的都会死 //如果杀的人最少,那还得看targ的入度是不是还有别的 if(!ipt[targ]) que[++sze] = targ; } for(int i=1; i<=n; i++) { //为什么满足这个条件就一定在环里? //如果他不在环里,1是已经死了,如果还没死,被救一次就减一个入度 //根据上面的循环,一个不在环里的点只有死掉和入度剩下0两种情况 if(ipt[i] && !kill[i]) { int ring = 0;//ring代表环的大小 bool flag = false; for(int j=i; !kill[j]; j=aim[j]) { ring++; if(exist[j]) flag = true; kill[j] = true; } if(!flag && ring > 1) lve++;//这是一个独立的环并且排除自杀的情况 sze += ring/2; } } printf("%d %d", n-sze, n-lve); return 0; }
D. 翻转游戏
题目描述
-
翻转游戏是在一个 4*4的正方形上进行的,在正方形的 4 个格上每个格子都放着一个双面的物件。每个物件的两个面,一面是白色,另一面是黑色,每个物件要么白色朝上,要么黑色朝上,每一次你只能翻 1 个物件,从而由黑到白的改变这些物件上面的颜色,反之亦然。每一轮被选择翻转的物件遵循以下规则:
- 从 16个物件中任选一个。
- 翻转所选择的物件的同时,所有与它相邻的左方物件、右方物件、上方物件和下方物件(如果有的话),都要跟着翻转。
-
以下为例:
bwbw wwww bbwb bwwb
- 这里 表示该格子放的物件黑色面朝上、 表示该格子放的物件白色朝上。如果我们选择翻转第三行的第一个物件,那么格子状态将变为:
bwbw bwww wwwb wwwb
-
游戏的目标是翻转到所有的物件白色朝上或黑色朝上。你的任务就是写一个程序来求最少的翻转次数来实现这一目标。
输入格式
输入文件包含 行,每行 个字符,每个字符 或 表示游戏开始时格子上物件的状态。
输出格式
输出文件仅一个整数,即从给定状态到实现这一任务的最少翻转次数。如果给定的状态就已经实现了目标就输出 0
,如果不可能实现目标就输出: 。
样例
样例输入
bwwb
bbwb
bwwb
bwww
样例输出
4
//copy #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e6 + 2; const int mod = 998244353; const ll INF = 0x7fffffff; int mp[6][6], n=4; ll ans; char s[5]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void flip(int x, int y) { mp[x-1][y] ^= 1; mp[x+1][y] ^= 1; mp[x][y-1] ^= 1; mp[x][y+1] ^= 1; mp[x][y] ^= 1; } void dfs(int lne, int stp, int opt) { if(lne == n+1) { bool flag = true; for(int i=1; i<=n; i++) { if(mp[n][i] != opt) flag = false; } if(flag) ans = min(ans, (ll)stp); return; } int s = 0; for(int i=1; i<=n; i++) { if(mp[lne-1][i] != opt) { flip(lne, i); s |= (1<<(i-1)); stp++; } } dfs(lne+1, stp, opt); for(int i=1; i<=n; i++) { if(s&(1<<(i-1))) flip(lne, i); } } int main() { freopen("flip.in", "r", stdin); freopen("flip.out", "w", stdout); for(int i=1; i<=n; i++) { scanf("%s", s); for(int j=0; j<n; j++) { if(s[j] == 'b') mp[i][j+1] = 1; } } //把上面一行扔到下面一行处理 //这样可以在不改变上一行其它棋子的情况下把上一行化成目标状态 ans = INF; int Max = (1<<n)-1; for(int s=0; s<=Max; s++)//枚举第0行可能的状态,其实就是固定第一行的点击方式 { for(int i=1; i<=n; i++) { if(s&(1<<(i-1))) mp[0][i] = 1; else mp[0][i] = 0; } dfs(1, 0, 0); dfs(1, 0, 1); } if(ans == INF) printf("Impossible"); else printf("%lld", ans); return 0; }