2013ACM多校联合(1)
A:Non-negative Partial Sums
题意:给定一个序列,我们能够给这个序列做一个循环移位的操作,即把第一个数字放到整个序列的最后一位。问存在多少种移动后的状态满足对于所有的前缀和都大于等于0。
解法:对于一个长度为N的序列,设从1-i的前缀和为sum[i],假设已经有K个数字从前面移动到序列的后面,那么对于当前序列的前N-K个元素减少的值为移动到后面的K个数字的和,增加为0,因此从sum[K+1...N]从选择一个最小的减去sum[i]查看是否大于等于0即可。对于后面K个元素,增加的值为sum[N]-sum[i],减少的值为0,因此从sum[1...K]中选择一个最小的加上sum[N]-sum[i]查看是否大于等于0即可。只有当这个检查都大于等于0这个状态才是合法的。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> using namespace std; int N, seq[1000005]; int sum[1000005]; // 保留前缀和 int sma[1000005]; // 从前往后保留前缀和中最小的一个 int rsma[1000005];// 从后往前保留前缀和中最小的一个 int main() { int cnt; sma[0] = 0x3fffffff; while (scanf("%d", &N), N) { cnt = 0; rsma[N+1] = sma[0]; for (int i = 1; i <= N; ++i) { scanf("%d", &seq[i]); sum[i] = sum[i-1] + seq[i]; sma[i] = min(sma[i-1], sum[i]); } for (int i = N; i >= 1; --i) { rsma[i] = min(rsma[i+1], sum[i]); } if (sma[N] >= 0) cnt = 1; for (int i = 1; i < N; ++i) { if (sma[i]+sum[N]-sum[i] >= 0 && rsma[i+1]-sum[i] >= 0) { ++cnt; } } printf("%d\n", cnt); } return 0; }
按照题解上的解法用单调队列同样可以解决该题,将串扩充为原来的两倍,将这个保留1-2*N的前缀和,然后对于某一段区间(连续的N个元素)对应一种循环后的状态,使用队列保留从后往前的N个连续前缀和的单调队列,由于区间中每一个值减去前面的某一点区间就是循环后的前缀和,因此每次从队列中取出最小值,在这个最小值还没有过期(如果已经枚举到左边界为i号的区间,那么原先加入单调队列的序号大于 i+N-1 的元素就无效了)的情况下如果最小值减去前面某段区间和值大于等于0,那么这个状态就是合法的。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <queue> using namespace std; int N, seq[2000005]; int sum[2000005]; struct Statu { int sum, pos; bool operator < (Statu t) const { // 每次从队列中选取前缀和最小的元素并且编号最大的点,这样便于删除过期元素 if (sum != t.sum) return sum > t.sum; else return pos < t.pos; } Statu() {} Statu(int s, int p) : sum(s), pos(p) {} bool legal(int) const; }; bool Statu::legal(int x) const { // 判定一个元素是否过期 if (pos - x > N) return false; else return true; } queue<Statu>q; void deal() { int cnt = 0; Statu v; for (int i = N*2; i > N; --i) { while(!q.empty() && sum[i] <= q.front().sum) q.pop(); q.push(Statu(sum[i], i)); } for (int i = N; i >= 1; --i) { while (!q.front().legal(i)) q.pop(); v = q.front(); if (v.sum - sum[i] >= 0) { ++cnt; } while(!q.empty() && sum[i] <= q.front().sum) q.pop(); q.push(Statu(sum[i], i)); } printf("%d\n", cnt); } int main() { while (scanf("%d", &N), N) { while (!q.empty()) q.pop(); for (int i = 1; i <= N; ++i) { scanf("%d", seq+i); seq[N+i] = seq[i]; } for (int i = 1; i <= 2*N; ++i) { sum[i] = sum[i-1] + seq[i]; } deal(); } return 0; }
#include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <iostream> #include <queue> using namespace std; int N, seq[2000005]; int sum[2000005]; struct Statu { int sum, pos; bool operator < (Statu t) const { // 每次从队列中选取前缀和最小的元素并且编号最大的点,这样便于删除过期元素 if (sum != t.sum) return sum > t.sum; else return pos < t.pos; } Statu() {} Statu(int s, int p) : sum(s), pos(p) {} bool legal(int) const; }; bool Statu::legal(int x) const { // 判定一个元素是否过期 if (pos - x > N) return false; else return true; } Statu q[2000005]; int front, tail; void deal() { int cnt = 0; front = tail = 0; Statu v; for (int i = N*2; i > N; --i) { while(front != tail && sum[i] <= q[front].sum) ++front; q[tail++] = Statu(sum[i], i); } for (int i = N; i >= 1; --i) { while (!q[front].legal(i)) ++front; v = q[front]; if (v.sum - sum[i] >= 0) { ++cnt; } while(front != tail && sum[i] <= q[front].sum) ++front; q[tail++] = Statu(sum[i], i); } printf("%d\n", cnt); } int main() { while (scanf("%d", &N), N) { for (int i = 1; i <= N; ++i) { scanf("%d", seq+i); seq[N+i] = seq[i]; } for (int i = 1; i <= 2*N; ++i) { sum[i] = sum[i-1] + seq[i]; } deal(); } return 0; }
B:拯救猫咪
题意:一只猫咪想要去食堂,猫咪的世界是一维的,即其只能够在直线上行走,现在有N条狗进行巡逻,猫的每一次移动都必须在N只狗的视线外,猫如果不能够移动就在原地休息,每次能够增加一点能量e,初始化能量为0,每一秒钟行走的距离为e+1。
解法:可以将原地休息看做是一种策略,遗憾的是这种策略并没有带来实际的好处(增加的能力在以后能够多走的距离和用一秒钟走一格是等效的),反而能走的话尽量走远时最优的策略。我采用的是一秒一秒的模拟,在没有对无效的狗进行动态删除的情况下TLE。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <vector> using namespace std; int D, N; struct Dog { int pos; // 当前的位置 int sp; // 行走的速度 int wide; // 视野宽度 int l, r; // 视野区间 void update(); const Dog & read(); }; vector<Dog>v; void Dog::update() { l = pos - sp - wide; r = pos + wide; } const Dog & Dog::read() { scanf("%d %d %d", &pos, &sp, &wide); update(); return *this; } struct Cat { int pos; // 当前的位置 int e; // 当前的能量 Cat() { pos = e = 0; } bool finish(); }; bool Cat::finish() { return pos >= D; } int main() { while (scanf("%d %d", &D, &N) && D!=-1||N!=-1) { v.clear(); int ti = 0; Cat cat; // 由默认构造函数自动初始化 for (int i = 0; i < N; ++i) { v.push_back(Dog().read()); } while (!cat.finish()) { // 如果没有走到终点 int cl = cat.pos, cr = cl+cat.e+1; // 计算出猫下一秒能够行走的区间 bool stop = false; for (int i = 0; i < v.size(); ++i) { while (i < v.size() && v[i].r < cl) { v.erase(v.begin()+i); } if (v[i].l > cr || v[i].r < cl) {} else if (v[i].l - cl > 1){ // 能够放弃跳跃更远而选择更短的距离 cr = min(v[i].l-1, cr); } else { stop = true; } v[i].pos -= v[i].sp; // 对dog的状态进行一个更新 v[i].update(); } if (stop) { cat.e += 1; // 停滞1秒钟,能量加1 } else { cat.pos = cr; cat.e -= cr-cl-1; } ++ti; // 时间前进一秒 } printf("%d\n", ti); } return 0; }
C:兔纸的难题
题意:有N只兔纸,这些兔纸一次进栈,兔纸只有黑白两种颜色,如果栈顶的三个元素是相同颜色话,这些兔纸就会跳出来,现在问N只兔纸能够跳出来的序列方案一共有多少种?
解法:不是很好想的dp题,考虑dp[i][j]表示还剩下i只兔纸,而栈中还有j只兔纸没被消掉的方案数,两种转移,一种是上一只兔纸造成了消除,一种是没造成消除。因此dp[i][j]=dp[i-1][j+1]+dp[i-1][j-2],另外注意j==2的时候,dp[i][2]=dp[i-1][3]+2*dp[i-1][0],这是因为这种情况下,兔纸的颜色就任意了,而之前的方程兔纸的颜色被限制了。
代码如下:
#include <cstdlib> #include <cstdio> #include <iostream> #include <cstring> using namespace std; const int MOD = int(1e9)+7; int dp[1005][1005]; int N; void solve() { dp[0][0] = 1; for (int i = 1; i <= 1000; ++i) { for (int j = 0; j <= 1000; ++j) { if (j + 1 <= 1000) { dp[i][j] = dp[i-1][j+1]; dp[i][j] %= MOD; } if (j - 2 >= 0) { dp[i][j] += dp[i-1][j-2]; dp[i][j] %= MOD; if (j == 2) { dp[i][j] += dp[i-1][j-2]; dp[i][j] %= MOD; } } } } } int main() { solve(); while (scanf("%d", &N), N!=-1) { printf("%d\n", dp[N][0]); } return 0; }
D:二哥的内存
题意:虚拟出一个100000*100000的数组内存空间,现在这个内存空间初始化为0,并且对其中的某些元素进行赋初值。后接着一系列的操作使得这个数组的行和列得以交换,中间还对某一行或者是某一列元素的值进行询问。
解法:使用两个数组对第i行和第j列对应的原始的行和列进行保留,由于行和列的交换是独立的,因此只要每次对保留的原始行和列进行交换即可。通过map来处理经过赋初值的值。不知道是不是后台数据有问题,是用 r*100000+j 的hash处理方式竟然一直WA,换成100005后才得以AC。
代码如下:
#include <iostream> #include <cstring> #include <cstdio> #include <algorithm> #include <cstdlib> #include <map> using namespace std; int r[100010], c[100010]; int N, M; map<long long,int>mp; void swap(int &a, int &b) { int t = a; a = b; b = t; } int main() { while (scanf("%d", &N), N != -1) { int x, y, z; mp.clear(); for (int i = 0; i < 100005; ++i) { r[i] = c[i] = i; } for (int i = 0; i < N; ++i) { scanf("%d %d %d", &x, &y, &z); mp[1LL*x*100005+y] = z; } scanf("%d", &M); for (int i = 0; i < M; ++i) { scanf("%d %d %d", &z, &x, &y); if (z == 0) { swap(r[x], r[y]); } else if (z == 1) { swap(c[x], c[y]); } else { if (mp.count(1LL*r[x]*100005+c[y])) { printf("%d\n", mp[1LL*r[x]*100005+c[y]]); } else { printf("0\n"); } } } } return 0; }
E:Wally World
题意:给定两个坐标点表示两个情人的位置,然后给定一堵墙(也是由两个坐标确定),且这堵墙一定平行x轴或者是y轴。在不能穿过墙的前提下问两人最少的时间是多久。
解法:首先判定这两个点是不是在直线的某一侧(由于线是平行于坐标轴的,因此这个判定就变得很简单了)或者连线与给定的墙没有交点,这样就可以计算出距离再除以2了。否则的话答案就肯定是要经过墙两头的某一个点,因此对两种方式进行比较就行了。不知道哪个头文件定义了y1,y2类似的变量,没办法在这个头文件后面用了#define来避嫌。
代码如下:
#include <cstdlib> #include <cstring> #include <algorithm> #include <cmath> #include <cstdio> using namespace std; #define x1 xx1 #define x2 xx2 #define y1 yy1 #define y2 yy2 int sx, sy, ex, ey; int x1, y1, x2, y2; double dist(double x1, double y1, double x2, double y2) { return sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2)); } bool judge(int f) { if (f == 1) { // 如果直线与y轴平行 if (sy == ey) { return sy <= min(y1, y2) || sy >= max(y1, y2); } else { double k = 1.0*(ey-sy) / (ex-sx); double b = sy - k*sx; double yy = k*x1+b; return yy <= min(y1, y2) || yy >= max(y1, y2); } } else { if (sx == ex) { return sx <= min(x1, x2) || sx >= max(x1, x2); } double k = 1.0*(ey-sy) / (ex-sx); double b = sy - k*sx; double xx = (y1-b) / k; return xx <= min(x1, x2) || xx >= max(x1, x2); } } int main() { int ca = 0; while (scanf("%d %d %d %d", &sx, &sy, &ex, &ey), sx|sy|ex|ey) { scanf("%d %d %d %d", &x1, &y1, &x2, &y2); printf("Case %d: ", ++ca); if (x1 == x2) { // 如果直线与y轴平行 if ((sx-x1)*(ex-x1)>0 || judge(1)) { // 同侧 printf("%.3f\n", dist(sx, sy, ex, ey) / 2); } else { double ans1 = dist(sx, sy, x1, y1) + dist(ex, ey, x1, y1); double ans2 = dist(sx, sy, x2, y2) + dist(ex, ey, x2, y2); printf("%.3f\n", min(ans1, ans2) / 2); } } else { // 如果之间与x轴平行 if ((sy-y1)*(ey-y1)>0 || judge(2)) { printf("%.3f\n", dist(sx, sy, ex, ey) / 2); } else { double ans1 = dist(sx, sy, x1, y1) + dist(ex, ey, x1, y1); double ans2 = dist(sx, sy, x2, y2) + dist(ex, ey, x2, y2); printf("%.3f\n", min(ans1, ans2) / 2); } } } return 0; }
F:小云过生日
题意:有N个格子,这些格子里面可能放着糖果,现在给定M块纸片要求将这些糖果都覆盖到,每块纸片的长度是任意的,问所有纸片加起来一共最少多长能够覆盖所有的糖果。
解法:将放在一个格子里的糖果视作一颗糖果并将连续在一起的糖果看做是一个整体,整体与整体存在一定的空隙。假设给定的糖果能够组成K个整体,那么就有K-1个间隙,如果M>=K,那么覆盖所有糖果就是去重之后的糖果数了。如果M<K,如果K-M=1,那么说明一定有一块纸片要覆盖两个连续的糖果整体,包含一个空隙。同理如果K-M=i,那么覆盖方案就要包括i个空隙,因此对空隙进行排序,每次取最小的空隙即可。
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <cmath> using namespace std; int N, M, S; int seq[200005], rev[200005]; int idx, sum[200005]; int main() { while (scanf("%d %d %d", &M, &N, &S), N|M|S) { idx = 0; for (int i = 0; i < S; ++i) { scanf("%d", &seq[i]); } sort(seq, seq+S); S = unique(seq, seq+S) - seq; int last, p = 1; while (seq[p] == seq[p-1] + 1) ++p; last = seq[p-1]; for (int i = p; i < S; ++i) { if (seq[i] == seq[i-1] + 1) { last = seq[i]; continue; } else { rev[idx++] = seq[i]-last-1; last = seq[i]; } } sort(rev, rev+idx); sum[0] = rev[0]; for (int i = 1; i < idx; ++i) { sum[i] = sum[i-1] + rev[i]; } if (idx+1 <= M) { printf("%d\n", seq[S-1]-seq[0]+1-sum[idx-1]); } else { printf("%d\n", seq[S-1]-seq[0]+1-sum[idx-1]+sum[idx-M]); } } return 0; }
G:数学
题意:给出数列A1,A2 ,...,AN,并设
现要求把所有的Bi 算出来。
解法:保留一个前缀积和后缀积即可。
代码如下:
#include <cstdlib> #include <cstring> #include <iostream> #include <cstdio> using namespace std; const int MOD = 1000000007; int N, rec[100005], rrec[100005]; int seq[100005]; int main() { while (scanf("%d", &N), N) { rec[0] = rrec[N+1] = 1; for (int i = 1; i <= N; ++i) { scanf("%d", &seq[i]); rec[i] = (1LL * rec[i-1] * seq[i]) % MOD; } for (int i = N; i >= 1; --i) { rrec[i] = (1LL * rrec[i+1] * seq[i]) % MOD; } for (int i = 1; i <= N; ++i) { printf(i == 1 ? "%d" : " %d", (1LL * rec[i-1] * rrec[i+1]) % MOD); } puts(""); } return 0; }
H:跳格子
题意:给定一张N*M的图,问从起点到终点刚好走S步能不能走到。
解法:bfs搜索,对走过的点进行标记,在到达的时候如果存在所走步数小于等于S并且与S的差值为偶数的点。
代码如下:
#include <iostream> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; int N, M, S, sx, sy; char mp[15][15]; bool judge(int x, int y) { if (x >= 1 && x <= N && y >= 1 && y <= M) { return true; } else return false; } #include <queue> struct Node { int x, y, step; }info, pos; char vis[15][15]; int dir[4][2] = {1, 0, -1, 0, 0, 1, 0, -1}; bool bfs() { queue<Node>q; memset(vis, 0, sizeof (vis)); info.x = sx, info.y = sy, info.step = 0; vis[sx][sy] = 1; q.push(info); while (!q.empty()) { pos = q.front(); if (mp[pos.x][pos.y] == 'D' && pos.step <= S && (S-pos.step)%2==0) { return true; } q.pop(); for (int i = 0; i < 4; ++i) { int xx = pos.x + dir[i][0]; int yy = pos.y + dir[i][1]; if (judge(xx, yy) && mp[xx][yy] != 'X' && !vis[xx][yy]) { info.x = xx, info.y = yy, info.step = pos.step + 1; vis[xx][yy] = 1; q.push(info); } } } return false; } int main() { while(scanf("%d %d %d", &N, &M, &S), N|M|S) { for (int i = 1; i <= N; ++i) { scanf("%s", mp[i] + 1); for (int j = 1; j <= M; ++j) { if (mp[i][j] == 'S') { sx = i, sy = j; } } } puts(bfs() ? "YES" : "NO"); } return 0; }
后记:由于题目已经做了修改,bfs每个状态需要记录路径,所以dfs更好处理,此代码自此WA。