2013ACM多校联合(4)
A:ZZ买衣服
简单的字符串处理问题,需要注意的是某一天买了之后接下来的天数中不能够再次购买。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <algorithm> #include <set> using namespace std; int N, M; set<string>st; int main() { char str[100]; while (scanf("%d %d", &N, &M) != EOF) { st.clear(); for (int i = 0; i < N; ++i) { scanf("%s", str); st.insert(str); } for (int i = 0; i < M; ++i) { scanf("%s", str); if (st.count(str)) { puts("NO"); } else { st.insert(str); puts("YES"); } } } return 0; }
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> #include <iostream> #include <cmath> using namespace std; typedef unsigned long long LL; const int MOD = 200003; const int T = 29; struct Node { int key, next; LL val; }e[200005]; int head[200005]; int N, M, idx; LL getval(char *str) { int len = strlen(str); LL val = 0; for (int i = 0; i < len; ++i) { val = val * T + str[i]-'a'+1; } return val; } void insert(int key, LL val) { e[idx].key = key, e[idx].val = val; e[idx].next = head[key]; head[key] = idx++; } void encode(char *str) { LL val = getval(str); int key = val % MOD; insert(key, val); } bool search(char *str) { LL val = getval(str); int key = val % MOD; for (int i = head[key]; i != -1; i = e[i].next) { if (e[i].val == val) { return true; } } return false; } int main() { /* for (int i = 2; i <= (int)sqrt(1.0*MOD); ++i) { if (MOD % i == 0) { puts("is not prime"); } }*/ char str[50]; while (scanf("%d %d", &N, &M) != EOF) { idx = 0; memset(head, 0xff, sizeof (head)); for (int i = 0; i < N; ++i) { scanf("%s", str); encode(str); } for (int i = 0; i < M; ++i) { scanf("%s", str); if (!search(str)) { puts("YES"); encode(str); } else { puts("NO"); } } } return 0; }
B:ZZ的橱柜
通过构造得出N个序列的前K大,求所有序列中的前K大,使用优先队列维护一个长度为N的序列,时间复杂度为O(N*log(N))。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <algorithm> #include <set> #include <queue> using namespace std; const int INF = 0x7fffffff; int A[400005], B[400005]; int N, M, idx; struct Node { int x, y, val; Node(int xx, int yy, int v) : x(xx), y(yy), val(v) {} Node(){} }; bool operator < (const Node & a, const Node & b) { return a.val > b.val; } void solve() { Node t; priority_queue<Node>q; for (int i = 1; i <= N; ++i) { q.push(Node(i, 1, A[i]+B[1])); } int cnt = 0; while (cnt < M) { t = q.top(); printf("%d\n", t.val); q.pop(); q.push(Node(t.x, t.y+1, A[t.x]+B[t.y+1])); ++cnt; } } int main() { while (scanf("%d %d", &N, &M) != EOF) { idx = 0; for (int i = 1; i <= N; ++i) { scanf("%d", &A[i]); } sort(A+1, A+1+N); for (int i = 1; i <= N; ++i) { scanf("%d", &B[i]); } sort(B+1, B+1+N); solve(); } return 0; }
C:ZZ的小游戏
8数码问题,bfs搜索,通过康托展开来hash一个全排列的序列,也就是通过逆序对和递增进制数的一个一一对应。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int MAXN = 362885; int fac[10] = {1,1,2,6,24,120,720,5040,40320}; int ret[MAXN]; // 9! = 362880, 一共有这么多种状态 struct Node { char sta[10]; int step; }; Node que[MAXN]; int idx, front, tail; int dir[4][2] = {0, 1, 0, -1, 1, 0, -1, 0}; int get(char str[]) { int k, key = 0; for (int i = 0; i < 8; ++i) { k = 0; for (int j = i+1; j < 9; ++j) { if (str[j] < str[i]) { // 求逆序对的个数,这个数刚好和递增进制数契合 ++k; } } key += k * fac[8-i]; } return key; } bool judge(int x, int y) { if (x < 0 || x >= 3 || y < 0 || y >= 3) return false; return true; } void bfs() { Node pos; for (int i = 0; i < 9; ++i) { pos.sta[i] = 8-i; } pos.step = 0; idx = front = tail = 0; int key = get(pos.sta); ret[key] = pos.step; que[tail++] = pos; while (front != tail) { pos = que[front++]; int ze; for (int i = 0; i < 9; ++i) { if (pos.sta[i] == 0) { ze = i; break; } } for (int k = 0; k < 4; ++k) { int zx = ze/3+dir[k][0]; int zy = ze%3+dir[k][1]; if (!judge(zx, zy)) continue; swap(pos.sta[ze], pos.sta[zx*3+zy]); pos.step += 1; key = get(pos.sta); if (!(~ret[key])) { ret[key] = pos.step; que[tail++] = pos; } // 没有增加新的状态,原地修改后直接在原来的状态上回溯 swap(pos.sta[ze], pos.sta[zx*3+zy]); pos.step -= 1; } } } int main() { memset(ret, 0xff, sizeof (ret)); bfs(); int T; char str[10]; scanf("%d", &T); while (T--) { int c; for (int i = 0; i < 9; ++i) { scanf("%d", &c); str[i] = c; } int key = get(str); if (!(~ret[key])) { puts("impossible!"); } else { printf("%d\n", ret[key]); } } return 0; }
D:ZZ的计算器
模拟,自己使用递归写的,在处理乘法和除法的时候要从后面开始,反正这题写的不是很好。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <cctype> using namespace std; typedef long long LL; char str[100]; bool legal; LL deal(int l, int r) { LL t = 0; int i = l, sign = 1; if (str[i] == '+') { i = l + 1; } else if (str[i] == '-'){ sign = -1; i = l + 1; } int j; for (j = r; j >= i; --j) { if (!isdigit(str[j])) { break; } } for (int k = j+1; k <= r; ++k) { t = t * 10 + str[k] - '0'; } if (j < i) return sign * t; if (str[j] == '*') { return sign * deal(i, j-1) * t; } else { if (t == 0) { legal = false; return 1; } else { return sign * deal(i, j-1) / t; } } } LL solve(int l, int r) { LL t = 0; int i = l, sign = 1; if (str[l] == '-') { sign = -1; i = l + 1; } else if (str[l] == '+') { i = l + 1; } while (isdigit(str[i]) && i <= r) { t = t * 10 + str[i] - '0'; ++i; } t *= sign; if (i > r) return t; if (str[i] == '+' || str[i] == '-') { return t + solve(i, r); } else { int j; for (j = i+1; j <= r; ++j) { if (str[j] == '+' || str[j] == '-') { break; } } if (j > r) { return deal(l, r); } return deal(l, j-1) + solve(j, r); } } int main() { while (scanf("%s", str) != EOF) { legal = true; LL ans = solve(0, strlen(str)-1); if (legal) { printf("%lld\n", ans); } else { puts("impossible"); } } return 0; }
E:ZZ的研究
首先将500以内的素数全部找出来,然后对每一个数得出一个关于素因子个数的向量
设N个数分别是a1,a2...an。且已知500以内从大到小的素数为p1,p2...pk。对于ai进行素因子分解之后有:,对于任何一个数将其所有的素因子的指数单独拿出来后,N个数可以构成一下矩阵(如图a)。
a b c
现在我们回到原问题,从N个数中选择出若干个数,是的他们的乘积的平方根为一个整数。换句话说也就是要求他们乘出来后的结果素因子分解的指数均为偶数。也就是说某一个数的某个指数为偶数那么就和为0没有区别,为奇数就和1没有区别,因此矩阵的每一个元素均可以作一个mod 2 的处理,只关心各个值的奇偶性(如图b)。
对于原问题,就可以转化为求一个线性模2加方程组问题(如图c)。
其中x1,x2...xn的取值都非0即1(对应于一个数取或者是不取),这也正符合模2加方程的特点:所有元素都只需要考虑其奇偶性。若方程的解唯一,那么未知数向量的奇偶性就唯一;若不唯一,那么不定的未知量就各有两个取值,而非一般情况下的无数组解。
先求解如下:假设有n个元素,首先c图中左边矩阵中的每一个元素对2取模,然后求这个矩阵的秩,如果秩为r,那么有r个未知量值必须为0,其余两可以为0或者是1任意值,由于必须从集合中选出元素,因此最终的结果就是[2^(n-r)]-1。
代码如下:
#include <cstdlib> #include <cstring> #include <cstdio> #include <iostream> #include <algorithm> #include <cmath> using namespace std; typedef long long LL; int p[505], num[100]; int idx, N; struct Matrix { int r, c; int a[105][105]; void init(int rr, int cc) { r = rr, c = cc; memset(a, 0, sizeof (a)); } void rswp(int x, int y, int s) { if (x == y) return; for (int i = s; i <= c; ++i) { swap(a[x][i], a[y][i]); } } // 高斯消元的过程其实就是讲上面一行作为条件的代入的过程 void relax(int x, int y, int s) { for (int i = s; i <= c; ++i) { a[y][i] ^= a[x][i]; } } int rank() { int k = 1; // k表示未使用到的开始行 for (int j = 1; j <= c; ++j) { int hav = 0; for (int i = k; i <= r; ++i) { if (a[i][j]) { hav = i; break; } } if (!hav) continue; rswp(k, hav, j); for (int i = hav+1; i <= r; ++i) { if (a[i][j]) { relax(k, i, j); } } ++k; } return k-1; } }; // 定义一个系数矩阵 Matrix M; void pre() { idx = 1; int LIM = (int)sqrt(500.0); for (int i = 4; i <= 500; i+=2) { p[i] = 1; } for (int i = 3; i <= LIM; i+=2) { int k = 2*i; for (int j = i*i; j <= 500; j+=k) { p[j] = 1; } } for (int i = 2; i < 500; ++i) { if (!p[i]) { num[idx++] = i; } } // 共95个素因子 // printf("idx = %d\n", idx); } void deal(int s, LL x) { int cnt; for (int i = 1; i < idx; ++i) { if (x % num[i] == 0) { cnt = 0; while (x % num[i] == 0) { ++cnt; x /= num[i]; } if (cnt & 1) M.a[i][s] = 1; // 第a个数在第j个素数的贡献上为1 } } } int main() { pre(); int T; scanf("%d", &T); while (T--) { LL x; scanf("%d", &N); M.init(idx-1, N); for (int i = 1; i <= N; ++i) { scanf("%lld", &x); deal(i, x); } printf("%lld\n", (1LL<<(N-M.rank()))-1); } return 0; }
这题首先要找到每一个数的这样一种分解:任何一个数都能够被唯一分解成不相邻的若干个菲波那契数之和。如果把分解看作是是否第i个菲波那契数的向量的话。也就是1的数量最少的一个向量。对于题中所要求计算的其他分解方式就是在这个基础上进行的,问题转化为某一位的1分或者是不分。
例如:105 = 3 + 13 + 89 对应于向量 0010010001。
设状态dp[i][0]表示第i个菲波那契数进行不进行分解的方案数,dp[i][1]表示第i个菲波那契数进行分解。这里有这样一个推论:某一个1有多少种分解是一个其前面有多少个0的函数。
例如考虑 0000001 存在这样的分解路线 0000001 -> 0000110 -> 00110100 -> 11010100
观察到每一分解后一个1总是无法再往前移动的,而只有前面一个1能够往前移动,如果0的个数为x的话,移动的次数恰好为x/2。
如此便有动态规划方程:
dp[i][1] = dp[i-1][0] + dp[i-1][1]
dp[i][0] = dp[i-1][0] * (相邻1之间0的个数加1)/2 + dp[i-1][1] *(相邻1之间0的个数)/2
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long LL; LL fi[90]; LL N; int dp[90][2]; int idx, a[90]; // 其中a数组使用来记录哪个位置有1 void pre() { fi[0] = fi[1] = 1; int idx, i; for (i = 2; i <= 86; ++i) { fi[i] = fi[i-1] + fi[i-2]; } // 第86个数是小于10^18且最接近10^18的数 } void DP() { memset(dp, 0, sizeof (dp)); dp[0][1] = 1; for (int i = 1; i < idx; ++i) { dp[i][1] = dp[i-1][0] + dp[i-1][1]; dp[i][0] = dp[i-1][1] * ((a[i]-a[i-1]-1)/2) + dp[i-1][0] * ((a[i]-a[i-1])/2); } printf("%d\n", dp[idx-1][0] + dp[idx-1][1]); } void solve() { idx = 1; for (int i = 86; i >= 1; --i) { if (N >= fi[i]) { N -= fi[i]; a[idx++] = i; // 表示i位置有一个1 } } for (int i = 1, j = idx-1; i < j; ++i, --j) { swap(a[i], a[j]); } DP(); } int main() { pre(); int T; while (scanf("%d", &T) != EOF) { while (T--) { scanf("%lld", &N); solve(); } } return 0; }
G:ZZ的搬砖难题
简单网络流
代码如下:
#include <cstring> #include <cstdlib> #include <cstdio> #include <queue> #define INF 0x3fffffff #define RE(x) (x)^1 #define MAXN 10005 using namespace std; int N, M, head[MAXN], lvl[MAXN], idx; int SS, TT; struct Edge { int No, cap, next; }e[1005]; void init() { idx = -1; memset(head, 0xff, sizeof (head)); } void insert(int x, int y, int z) { ++idx; e[idx].No = y; e[idx].cap = z; e[idx].next = head[x]; head[x] = idx; ++idx; e[idx].No = x; e[idx].cap = 0; e[idx].next = head[y]; head[y] = idx; } bool bfs() { int pos; queue<int>q; memset(lvl, 0xff, sizeof (lvl)); lvl[SS] = 0; q.push(SS); while (!q.empty()) { pos = q.front(); q.pop(); for (int i = head[pos]; i!=-1; i = e[i].next) { if (e[i].cap > 0 && lvl[e[i].No]==-1) { lvl[e[i].No] = lvl[pos]+1; q.push(e[i].No); // 注意不要直接把“i”push进去了 } } } return lvl[TT] != -1; } int dfs(int u, int flow) { if (u == TT) { return flow; // 结束条件,把流推到了T节点(汇点) } int tf = 0, f; for (int i = head[u]; i != -1; i = e[i].next) { if (lvl[u]+1 == lvl[e[i].No] && e[i].cap > 0 && (f = dfs(e[i].No, min(e[i].cap, flow - tf)))) { e[i].cap -= f; e[RE(i)].cap += f; tf += f; } } if (tf == 0) { lvl[u] = -1; // tf等于零表示该点没有进行增广的能力,应及时将其高度赋值为-1,防止第二次被搜索到 } return tf; } int main() { int ans, a, b, c, M; while (scanf("%d %d", &SS, &TT) != EOF) { init(); ans = 0; scanf("%d", &M); for (int i = 0; i < M; ++i) { scanf("%d %d %d", &a, &b, &c); insert(a, b, c); } while (bfs()) { ans += dfs(SS, INF); } printf("%d\n", ans); } return 0; }
简单题,统计下袋子总和各个袋子的的奇偶性即可。
代码如下:
#include <cstdlib> #include <cstdio> #include <cstring> #include <iostream> #include <map> #include <algorithm> #include <set> using namespace std; int N; int seq[100005]; int ret1, ret2, tot; int main() { while (scanf("%d", &N) != EOF) { ret1 = ret2 = tot = 0; for (int i = 1; i <= N; ++i) { scanf("%d", &seq[i]); if (seq[i] & 1) ++ret1; else ++ret2; tot += seq[i]; } printf("%d\n", (tot & 1) ? ret1 : ret2); } return 0; }