这些东西发上来没坏事。
未完待续=。=......
chrous:
题意:略
傻Xdp,直接设计两个状态f[ i , j], g[ i , j]分别表示形成i~j一段,最后放得在最左/最右的方案数,直接转移即可。
# include <cstdlib> # include <cstdio> # include <cmath> using namespace std; const int mo = 19650827, maxn = 1000+10; int f[maxn][maxn], g[maxn][maxn]; int i, j, l, r, n, h[maxn]; int main() { freopen("chorus.in", "r", stdin); freopen("chorus.out", "w", stdout); scanf("%d", &n); for (i = 1; i <= n; i++) scanf("%d", &h[i]); for (i = 1; i <= n; i++) f[i][i] = 1; for (r = 2; r <= n; r++) for (l = r-1; l >= 1; l--) { f[l][r] = ((h[l]<h[l+1])*f[l+1][r] + (h[l]<h[r])*g[l+1][r]) % mo; g[l][r] = ((h[r]>h[l])*f[l][r-1] + (h[r]>h[r-1])*g[l][r-1]) % mo; } printf("%d", (f[1][n]+g[1][n])% mo); return 0; }
planar:
题意:给定一个图的哈密顿回路和所有的边,问其是否可能是平面图;点n <= 200, 边m<=10000,100组数据。
利用题目给出的哈密顿回路(我们需要其实只要哈密顿路径就可以了)
想象把哈密顿路径“扯直”,那么其余所有的边都必定分居路径的两侧。
如果两条边放在同一侧会相交,那么他们必定放在异侧,最后判断是否矛盾。
2-sat,甚至简单的并查集都可以解决这个问题。
唯一的问题是o(m^2)的枚举边是否相交太慢了,一直想找到合适的方法优化,结果…….结果…….原来平面图o(m) = o(n)(被坑了=。=!), 实际上m <= 3*n-6 =.=!,那么判一判,直接裸就行了!
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int maxn = 10000 + 5; int ufs[maxn*4], rel[maxn*4]; int g[maxn], x[maxn*4], y[maxn*4], tmp; int n, m; int find (int x) { int y; if (ufs[x] == x) return x; else { y = find(ufs[x]); rel[x] = rel[x] ^ rel[ufs[x]]; return ufs[x] = y; }; } bool work() { int fi, fj, i, j, k; for (i = 1; i<= m; i++) if (g[x[i]] > g[y[i]]) tmp = x[i], x[i] = y[i], y[i] = tmp; for (i = 1; i<= m; i++) ufs[i] = i, rel[i] = 0; for (i = 1; i<= m; i++) for (j = 1; j <= m; j++) if (i != j) if (g[x[i]] < g[x[j]] && g[x[j]] < g[y[i]] && g[y[i]] < g[y[j]]) { fi = find(i); fj = find(j); if (fi != fj) ufs[fi] = fj, rel[fi] = 1^rel[i]^rel[j]; else if ((rel[i]^rel[j]) == 0) return false; } return true; } int main() { int test, i, k; freopen("planar.in", "r", stdin); freopen("planar.out", "w", stdout); scanf("%d", &test); for (int ssss = 1; ssss <= test; ssss++) { scanf("%d%d", &n, &m); memset(g, 0, sizeof(g)); memset(x, 0, sizeof(x)); memset(y, 0, sizeof(y)); memset(ufs, 0, sizeof(ufs)); memset(rel, 0, sizeof(rel)); int xx, yy, now = 0; for (i = 1; i<= m; i++) { scanf("%d%d", &xx, &yy); if (xx != yy)x[++now]=xx, y[now]=yy; } m = now; for (i = 1; i<= n; i++) { scanf("%d", &k); g[k] = i; }; if (m > 3*n) printf("NO\n"); else if (work()) printf("YES\n"); else printf("NO\n"); } return 0; }
fsk: 暂未做=。=!......题目都木有看懂=。=
bus:
题意: 公路上有1~n个站,k个公交车,1~k是他们的起点站,n-k+1~n是重点站。公交车从编号小的站开往编号大的站,一个站有且仅有一个公交车停靠,对于一个公交车,它停靠的任意两个站之间,编号距离不得超过p;
K,p <= 10, n <=10^9;
n相当大,k相当小,不用想就是状压矩乘了=。=!
但是如果直接记录每辆这距离当前的距离,那么信息量so 大了;
注意到公交车是无差别的,所以我们只需要记录距离当前位置p以内的所有公交车的距离就行了......有点绕。
反正状态被压缩到了C(9,5)以内,可以状压矩乘了。
# include <cstdlib> # include <cstdio> # include <cmath> # include <cstring> using namespace std; const int size = 130, mo = 30031; bool a[size][20]; int t[size][size], ml[size][size], c[size][size]; int top = 1, n, kn, p; void dfs(int have, int past) { int i; if (have == kn) { top++; for (i = 1; i <= kn; i++) a[top][i] = a[top-1][i]; } else for (i = past+1; i <= p; i++) { a[top][i] = true; dfs(have+1, i); a[top][i] = false; } } void prepare() { int flag ; int i, j, k; for (i = 1; i <= top; i++) for (j = 1; j <= top; j++) { flag = 1; for (k = 1; k <= p; k++) if ((a[i][k]^a[j][k+1])) flag --; if (flag == 0) c[i][j] = ml[i][j] = 1; } } void mul(int c[size][size], int a[size][size], int b[size][size]) { int i, j, k; memset(t, 0, sizeof(t)); for (i = 1; i <= top; i++) for (j = 1; j <= top; j++) for (k = 1; k <= top; k++) t[i][j] = (t[i][j]+a[i][k]*b[k][j]) % mo; for (i = 1; i <= top; i++) for (j = 1; j <= top; j++) c[i][j] = t[i][j]; } int main() { freopen("bus.in", "r", stdin); freopen("bus.out", "w", stdout); scanf("%d%d%d", &n, &kn, &p); a[top=1][1] = true; dfs(1, 1); top--;prepare(); for (n-=kn+1; n >0; n >>=1, mul(ml,ml,ml)) if (n & 1) mul(c,c,ml); // for (n-=kn+1; n >0; n--) // mul(c,c,ml); printf("%d", c[1][1]); return 0; }
题意:给定n堆石子,实现有几堆的石子被取走了,两人博弈取石子,一堆石子被取当且仅当它左边或右边的被取走。问先后手分别能去多少石子。
看到这道题,似曾相识中=。=,貌似去年一次考试出现了这个题目,那个时候是听了刘尧学长解释了好久才明白,最后也写得晕晕的。
仔细想了想大致回想起了思路, 统计两选手石子数目之差。取走的石子将这个序列分成了若干块,最理想的情况是:
↗, ↘↗,….., ↘↗, ↘,这样的情况直接贪心取就ok了;
但是情况肯定要复杂的多,但是 可以修复。
对于↘↗被破坏(单纯的↘或↗也可看做↘↗), 一定出现了↗↘的情况,即a[ i -1]< a[ i] , a[i]> a[i + 1], 对于这种情况,“先手”必取a[i- 1] 和a[i + 1], 后手必取a[I], 这样,三个数可以合并成a[i+1]+a[i-1]-a[i]; 而对于左右两边的 ↗和↘, 分别有 ↘,↗的情况与之破坏,这样的情况“先手”取必亏,大家都不愿意先取它,所以一定会留到最后,那么,谁回先取到它们可以推算出来,这样,它们可以提前统计出来,对于最后的石子,排序一个个取就可以了。
# include <cstdlib> # include <cstdio> using namespace std; const long long oo= (long long)1 << 62; const int maxn = 1000000+200; long long sum, ans; long long a[maxn], b[maxn]; int n, tot, next[maxn], pred[maxn]; long long tmp; void del(int v) { next[pred[v]] = next[v]; pred[next[v]] = pred[v]; } void maintain(int p) { if ((a[p] != -oo && a[pred[p]] != -oo && a[next[p]]!= -oo) &&(a[p] >= a[pred[p]] && a[p] >= a[next[p]])) { a[p] = a[pred[p]] + a[next[p]] - a[p]; a[pred[p]] = a[next[p]] = -oo; del(pred[p]); del(next[p]); maintain(pred[p]); maintain(p); maintain(next[p]); } } void change(int i, int j) { for (;b[i] != -oo && b[i+j] != -oo && b[i] > b[i+j]; b[i]=b[i+j]=-oo, i+=2*j) if (!(tot & 1)) ans += b[i+j]-b[i]; else ans -= b[i+j]-b[i]; } void sort(int l, int r) { int i = l, j = r; long long d = b[(l+r)>>1]; for (;i <= j;) { for (;b[i] > d; i++); for (;b[j] < d; j--); if (i <= j) tmp = b[i], b[i] = b[j], b[j] = tmp, i++, j--; } if (i < r) sort(i, r); if (l < j) sort(l, j); } int main() { int i, j; bool t1 = false, t2 = false; freopen("stone.in", "r", stdin); freopen("stone.out", "w", stdout); scanf("%d", &n); a[0] = -oo; a[n+1] = -oo; for (i = 1; i <= n; i++) { scanf("%d", &a[i]); sum += a[i]; if (!a[i]) a[i] = -oo; else ++tot; } if (a[1] == -oo) t1 = true; if (a[n] == -oo) t2 = true; for (i = 1; i <= n; i++) pred[i] = i-1, next[i] = i+1; for (i = 1; i <= n; i++) if (a[i] != -oo) maintain(i); for (b[0] = -oo, i = 1, j = 0; i <= n; i=next[i]) { if (a[i] != -oo) b[++j] = a[i]; else if (b[j] != -oo) b[++j] = -oo; } for (;b[j] == -oo;j--); n = j; b[0] = -oo; b[n+1] = -oo; if (!t1)change(1, 1); if (!t2)change(n, -1); sort(1, n); for (i = 1; i <= n && b[i] != -oo; i++) if (i & 1) ans += b[i]; else ans -= b[i]; printf("%I64d %I64d", sum+ans >> 1, sum-ans >> 1); return 0; }
......
Bounce:
题意:给定序列k[n],两个操作,一个修改操作,修改某个位置的k,一个询问操作,询问从i位置开始,每次跳跃到i+k[I], 问最后跳多少次可以跳出序列。
正解要用动态树的,结果无耻的用块链水过了。
把k分成sqrt(n), 统计每个位置跳出该块的步数以及跳出去会跳到哪里,这样每个询问都是sqrt(n)级别的,速度勉勉强强;
# include <cstdio> # include <cstdlib> # include <cmath> using namespace std; const int maxn = 300000; int aux[maxn], k[maxn], head[maxn], tail[maxn], go[maxn], cost[maxn]; int n, m, size, num; inline void change(int st, int d) { int i, id = aux[st]; k[st] = d; for (i = st; i >= head[id]; i--) if (i+k[i] > tail[id]) cost[i] = 1, go[i] = i+k[i]; else go[i] = go[i+k[i]], cost[i] = cost[i+k[i]]+1; } void prepare() { int i, j; size = (int) floor(sqrt(n))+1; for (i = 1, num = 0; i <= n; i+= size) head[++num] = i, tail[num] = i+size-1; tail[num] = n; for (i = 1; i <= num; i++) for (j = head[i]; j <= tail[i]; j++) aux[j] = i; for (i = 1; i <= num; i++) change(tail[i], k[tail[i]]); } inline int ask(int st) { int ans; for (ans = 0; st <= n; st = go[st]) ans += cost[st]; return ans; } int main() { int st, c, i, d; freopen("bounce.in", "r", stdin); freopen("bounce.out", "w", stdout); scanf("%d", &n); for (i = 1; i <= n; i++) scanf("%d", &k[i]); prepare(); scanf("%d", &m); for (i = 1; i <= m; i++) { scanf("%d", &c); if (c == 1) { scanf("%d", &st); st++; printf("%d\n", ask(st)); } else { scanf("%d%d", &st, &d); st++; change(st, d); } } return 0; }
Matrix:
题意:给定b[N][N], b[I][J]= a[i][j]+a[i-1][j]+a[i][j-1]+a[i-1][j-1], 求字典序最小的A
A的元素不超过10, n<= 200;
此题无耻的抄了别人的一个关于b,a和辅助数组c的公式:
Ai,j= Ci,j + (-1)^i+j-2 *A1,1 + (-1)^i-1*A1,j + (-1)^j-1*Ai,1 (i>1,j>1)
其中c[I][J] = B[I][J]-C[I-1][J]-C[I][J-1]-C[I-1][J-1];
那么,搜索第一行的数值,维护第一列的取值范围,出现矛盾则剪枝,这样搜就可以了。
吐槽:公式抄了别人的,结果那个公式的指数居然是错的,TAT,结果一直没调出来,wa,wa,wa,wa…………最后手算检验才发现公式的指数不对=。=!,抄别人的式子真不是好习惯。