8/13
2016 Multi-University Training Contest 2
官方题解
数学 A Acperience(CYD)
题意:
给定一个向量,求他减去一个 α(>=0)乘以一个值为任意+1或-1的B向量后得到向量,求这个向量膜的最小值
思路:
展开式子,当时最小,结果为。
代码:
#include <bits/stdc++.h> using namespace std; long long w,a,b,c,a2; long long gcd(long long x,long long y) { return y ? gcd(y,x%y) : x; } int main() { int T; int n; int i,j,k; scanf("%d",&T); while(T--) { scanf("%d",&n); a=0,b=1ll*n,a2=0; for(i=1;i<=n;i++) { scanf("%I64d",&w); if(w<0) w=-w; a+=w; a2+=w*w; } a=a*a; c=gcd(a,b); a/=c; b/=c; a=a2*b-a; c=gcd(a,b); a/=c; b/=c; printf("%I64d/%I64d ",a,b); } return 0; }
树形DP B Born Slippy(BH)
题意:
有一棵树,每个节点有权值。对于每一个节点s,使最大,其中v1,v2,...,vm编号的点都是前一个点的祖先(不一定是父亲),opt是位操作。
思路:
官方给出了一个很巧妙的做法,注意到而且又是位运算,然后因为是树,考虑树形DP。考虑dp[a][b],a表示的数的低8位,b表示数的高8位,dp意思是到当前状态下,下次要和a进行opt以及上一次opt的数是b的最优值。注意理解红字的含义,dp是交替的,对a转移,对b更新。我写的a和b与标程相反也是可以的,因为只要交替就可以,顺序无所。时间复杂度。
代码:
#include <bits/stdc++.h> using ll = long long; using uint = unsigned; const int MOD = 1e9 + 7; const int N = (1 << 16) + 5; char op[4]; int n; int w[N]; std::vector<int> edges[N]; uint opt(uint a, uint b) { if (op[0] == 'A') return a & b; if (op[0] == 'O') return a | b; return a ^ b; } uint dp[1<<8][1<<8]; //dp[低8位][高8位] uint backup[N][1<<8]; uint ans[N]; template<typename T> T _max(T &a, T b) { if (a == -1 || a < b) a = b; } void DFS(int u) { uint val = 0; uint a = w[u] & 255, b = w[u] >> 8; for (int i=0; i<256; ++i) { if (dp[i][b] != -1) _max (val, dp[i][b] + opt (i, a)); } ans[u] = val + w[u]; std::copy (dp[a], dp[a] + 256, backup[u]); for (int i=0; i<256; ++i) { _max (dp[a][i], val + opt (i, b) * 256); } for (int v: edges[u]) { DFS (v); } //back up std::copy (backup[u], backup[u] + 256, dp[a]); } uint solve() { memset (dp, -1, sizeof (dp)); DFS (1); uint ret = 0; for (int i=1; i<=n; ++i) { ret = (ret + (ll) i * ans[i] % MOD) % MOD; } return ret; } int main() { int T; scanf ("%d", &T); while (T--) { scanf ("%d%s", &n, op); for (int i=1; i<=n; ++i) { scanf ("%u", w+i); edges[i].clear (); } for (int i=2; i<=n; ++i) { int x; scanf ("%d", &x); edges[x].push_back (i); } printf ("%u ", solve ()); } return 0; }
归并树 D Differencia(BH)
题意:
简单来说就是两个数列a和b,两种操作:
1. 赋值[l, r]区间里a[i]为x
2. 询问[l, r]区间多少个a[i]>=b[i]
思路:
看了Claris的代码,强行看懂了。人家的题解写得已经很清楚了,不多说了。这题还有其他的做法,先学会了这一种。前提先知道归并树=归并排序+线段树(《挑战程序设计竞赛》P188),本题的关键是b数组不变,那么考虑第一操作的x对于b数组而言,现在有多少个数字<=x,用线段树维护。
代码:
#include <bits/stdc++.h> const int N = 1e5 + 5; const int MOD = 1e9 + 7; int A, B; const int C = ~(1<<31), M = (1<<16)-1; int rnd(int last) { int a = (36969 + (last >> 3)) * (A & M) + (A >> 16); int b = (18000 + (last >> 3)) * (B & M) + (B >> 16); return (C & ((a << 16) + b)) % 1000000000; } int dat[20][N], sum[20][2]; int tag[20][2]; int lid[20][N], rid[20][N]; int a[N], b[N]; int ql, qr, x; int n, m; void upl(int p, int o) { sum[o][0] = p ? p - l + 1 : 0; tag[o][0] = -1; } void upr(int p, int o) { sum[o][1] = p ? p - l + 1 : 0; tag[o][1] = p; } void push_up(int o) { sum[o][0] = sum[o][1] = sum[o+1][0] + sum[o+1][1]; } void push_down(int o) { if (tag[o][0]==-1 && tag[o][1]==-1) return ; if (tag[o][0] != -1) upl (lid[tag[o][0]], o+1); if (tag[o][1] != -1) upr (rid[tag[o][1]], o+1); tag[o][0] = tag[o][1] = -1; } void build(int l, int r, int o) { tag[o][0] = tag[o][1] = -1; if (l == r) { dat[o][l] = b[l]; sum[o][0] = sum[o][1] = a[l] >= b[l]; return ; } int mid = l + r >> 1; build (l, mid, o+1); build (mid+1, r, o+1); //merge int lp = l, rp = mid + 1, p = l; while (lp<=mid && rp<=r) { dat[o][p++] = dat[o+1][lp]<dat[o+1][rp] ? dat[o+1][lp++] : dat[o+1][rp++]; } while (lp <= mid) dat[o][p++] = dat[o+1][lp++]; while (rp <= r) dat[o][p++] = dat[o+1][rp++]; //rank lp = l; rp = mid + 1; for (int i=l; i<=r; ++i) { while (lp<=mid && dat[o+1][lp]<=dat[o][i]) lp++; while (rp<=r && dat[o+1][rp]<=dat[i][i]) rp++; lid[o][i] = lp - 1; rid[o][i] = rp - 1; if (lid[o][i] < l) lid[o][i] = 0; if (rid[o][i] <= mid) rid[o][i] = 0; } push_up (o); } void modify(int p, int l, int r, int o) { if (ql <= l && r <= qr) { } push_down (o); int mid = l + r >> 1, ret = 0; if (ql <= mid) modify (lid[p], l, mid, o+1); if (qr > mid) mofify (rid[p], mid+1, r, o+1); push_up (o); } int query(int p, int l, int r, int o) { if (ql <= l && r <= qr) { return sum[o][0]; } push_down (o); int mid = l + r >> 1, ret = 0; if (ql <= mid) ret += query (l, mid, o+1); if (qr > mid) ret += query (mid+1, r, o+1); return ret; } void solve() { build (1, n, 1); std::sort (b+1, b+1+n); int last = 0, ans = 0; for (int i=1; i<=m; ++i) { ql = rnd (last) % n + 1; qr = rnd (last) % n + 1; x = rnd (last) + 1; if (ql > qr) std::swap (ql, qr); if ((ql+qr+x) & 1) { int p = std::lower_bound (b+1, b+1+n, x) - b; modify (p, 1, n, 1); } else { int z = query (1, n, 1); ans = (ans + (ll) i * z % MOD) % MOD; last = 0; } } printf ("%d ", ans); } int main() { int T; scanf ("%d", &T); while (T--) { scanf ("%d%d%d%d", &n, &m, &A, &B); for (int i=1; i<=n; ++i) scanf ("%d", a+i); for (int i=1; i<=n; ++i) scanf ("%d", b+i); solve (); } return 0; }
极角排序 E Eureka(BH)
题意:
xjb推导之后发现就是求多少个子集共线。
思路:
听说这题用极角排序的要不卡常数,要不卡精度。题解做法是gcd求斜率,因为输入的是整数。计算子集的方法自己看。
代码:
#include <bits/stdc++.h> const double EPS = 1e-10; double PI = acos (-1.0); struct Point { int x, y; Point() {} Point(int x, int y) : x(x), y(y) {} bool operator < (const Point &rhs) const { return x < rhs.x || (x == rhs.x && y < rhs.y); } Point operator - (const Point &rhs) const { return Point (x-rhs.x, y-rhs.y); } bool operator == (const Point &rhs) const { return x == rhs.x && y == rhs.y; } void reduce() { int g = std::__gcd (abs (x), abs (y)); if (g) { x /= g; y /= g; } } void read() { scanf ("%d%d", &x, &y); } }; typedef long long ll; const int N = 1e3 + 5; const int MOD = 1e9 + 7; Point p[N], q[N]; int pow_two[N]; int n; void add_mod(int &a, int b) { a += b; if (a >= MOD) a -= MOD; } void init_pow() { pow_two[0] = 1; for (int i=1; i<N; ++i) { pow_two[i] = (ll) pow_two[i-1] * 2 % MOD; } } int solve() { std::sort (p, p+n); int ret = 0; for (int i=0; i<n; ++i) { int m = 0, cnt = 0; for (int j=i+1; j<n; ++j) { if (p[i] == p[j]) ++cnt; else q[m++] = p[j] - p[i]; } for (int j=0; j<m; ++j) { q[j].reduce (); } std::sort (q, q+m); add_mod (ret, pow_two[cnt] - 1); for (int x=0, y; x<m; x=y) { for (y=x; y<m && q[x] == q[y]; ++y); add_mod (ret, (ll) pow_two[cnt] * (pow_two[y-x]-1) % MOD); } } return ret; } int main() { init_pow (); int T; scanf ("%d", &T); while (T--) { scanf ("%d", &n); for (int i=0; i<n; ++i) { p[i].read (); } printf ("%d ", solve ()); } return 0; }
点双连通分量 F Fantasia(BH)
题意:
有一个无向图G,每个点有权值。定义表示删除点i后的图,表示,其中,意思是删除点i后,每一个连通子图的权值乘积的和。
思路:
先求点双连通分量,按照题解的做法,根据新点(bcc_cnt)和割顶建成新的图,然后树形DP计算:(v是u子树的点)。显然只有删除割顶时才会多出连通分量,那么先减去全部和,再加回多出来的分量。其他情况:不是割顶而且不是不是根节点,不会多出连通分量,所以只要除掉wi即可。
官方题解写到还有CDQ+并查集的做法,已经把他去年出的类似的题做掉了,这题待补。
代码:
#include <bits/stdc++.h> typedef long long ll; const int N = 1e5 + 5; const int M = 2e5 + 5; const int MOD = 1e9 + 7; int dfn[N]; int dfs_clock, bcc_cnt; bool is_cut[N]; std::vector<int> edges[N<<1], bcc[N<<1]; int sta[N]; int top; int w[N<<1], iw[N<<1]; int n, m; int pow_mod(int x, int n) { int ret = 1; for (; n; n>>=1) { if (n & 1) ret = (ll) ret * x % MOD; x = (ll) x * x % MOD; } return ret; } int Tarjan(int u, int fa) { int lowu = dfn[u] = ++dfs_clock; int child = 0; sta[top++] = u; for (int v: edges[u]) { if (!dfn[v]) { child++; int lowv = Tarjan (v, u); lowu = std::min (lowu, lowv); if (lowv >= dfn[u]) { is_cut[u] = true; bcc[++bcc_cnt].clear (); w[bcc_cnt] = 1; for (; ;) { int x = sta[--top]; w[bcc_cnt] = (ll) w[bcc_cnt] * w[x] % MOD; bcc[bcc_cnt].push_back (x); if (x == v) break; } bcc[bcc_cnt].push_back (u); w[bcc_cnt] = (ll) w[bcc_cnt] * w[u] % MOD; } } else if (dfn[v] < dfn[u] && v != fa) { lowu = std::min (lowu, dfn[v]); } } if (fa < 0 && child == 1) is_cut[u] = false; return lowu; } void find_bcc() { memset (dfn, 0, sizeof (dfn)); memset (is_cut, false, sizeof (is_cut)); dfs_clock = top = 0; bcc_cnt = n; for (int i=1; i<=n; ++i) { if (!dfn[i]) Tarjan (i, -1); } } void add_mod(int &a, int b) { a += b; if (a >= MOD) a -= MOD; if (a < 0) a += MOD; } int fa[N<<1]; int dp[N<<1], sum[N<<1], belong[N<<1]; bool vis[N<<1]; void DFS(int u, int pa, int o) { belong[u] = o; fa[u] = pa; vis[u] = true; dp[u] = w[u]; for (int v: edges[u]) { if (v == pa) continue; DFS (v, u, o); dp[u] = (ll) dp[u] * dp[v] % MOD; } if (u > n) { for (int v: bcc[u]) { belong[v] = o; vis[v] = true; } } } int solve() { find_bcc (); for (int i=1; i<=n; ++i) iw[i] = pow_mod (w[i], MOD - 2); for (int i=1; i<=bcc_cnt; ++i) { edges[i].clear (); } for (int i=n+1; i<=bcc_cnt; ++i) { for (int v: bcc[i]) { if (is_cut[v]) { edges[i].push_back (v); edges[v].push_back (i); w[i] = (ll) w[i] * iw[v] % MOD; } } } int sum = 0, ret = 0; memset (vis, false, sizeof (vis)); for (int i=n+1; i<=bcc_cnt; ++i) { if (!vis[i]) { DFS (i, -1, i); add_mod (sum, dp[i]); } } for (int i=1; i<=n; ++i) { if (!vis[i]) { DFS (i, -1, i); add_mod (sum, dp[i]); } } for (int i=1; i<=n; ++i) { int x = belong[i]; int tmp = sum; add_mod (sum, -dp[x]); if (!is_cut[i]) { if (i != belong[i]) add_mod (sum, (ll) dp[x] * iw[i] % MOD); } else { int tmp2 = (ll) dp[x] * pow_mod (dp[i], MOD - 2) % MOD; if (i != belong[i]) add_mod (sum, tmp2); for (int v: edges[i]) { if (v == fa[i]) continue; add_mod (sum, dp[v]); } } add_mod (ret, (ll) i * sum % MOD); sum = tmp; } return ret; } int main() { int T; scanf ("%d", &T); while (T--) { scanf ("%d%d", &n, &m); for (int i=1; i<=n; ++i) edges[i].clear (); for (int i=1; i<=n; ++i) { scanf ("%d", w+i); } for (int i=1; i<=m; ++i) { int u, v; scanf ("%d%d", &u, &v); edges[u].push_back (v); edges[v].push_back (u); } printf ("%d ", solve ()); } return 0; }
不会 G Glorious Brilliance
不懂 H Helter Skelter
hdu 5741 Helter Skelter 官方题解做法的详细证明
贪心 I It's All In The Mind(ZCJ)
题意:
思路:
代码:
不会 J Join The Future
贪心 K Keep On Movin(BH)
题意:
有若干个不同的字母,任意组合成若干个回文串,问某种组合使得其中最短的回文串长度最大。
思路:
好好反思,比赛时紧张,没想好就写,一直WA也是因为没想到奇数的字母也可以分配出来看成偶数字母添加回文串长度,被CYD点醒后立马AC。吸取教训,1. 写代码是一定要想好再写(特别是贪心乱搞题);2. 迟迟不过题,不能三人同时开题,需要交换题目或者合力先争取过某一题。
代码:
#include <bits/stdc++.h> const int INF = 0x3f3f3f3f; const int N = 1e5 + 5; int a[N]; int main() { int n; int sum[2]; int T; scanf ("%d", &T); while (T--) { int ans = INF; scanf ("%d", &n); sum[0] = sum[1] = 0; int m = 0; for (int i=1; i<=n; ++i) { int x; scanf ("%d", &x); if (x & 1) { ans = std::min (ans, x); a[m++] = x; } else { sum[0] += x; } } if (m == 0) { ans = sum[0]; printf ("%d ", ans); continue; } std::sort (a, a+m); int mn = a[0]; for (int i=1; i<m; ++i) { int tmp = a[i] - mn; a[i] = mn; sum[0] += tmp; } sum[0] /= 2; ans = mn + sum[0] / m * 2; printf ("%d ", ans); } return 0; }
DP+bitset L La Vie en rose(BH)
题意:
思路:
数据加强了,时限缩短一半,暴力很难跑过去了。标程都T了,好在铭神常数小,跑得快~。明天早上再看看
代码:
#include <bits/stdc++.h> using LL = long long ; #define ALL(v) (v).begin(),(v).end() #define showtime printf("time = %.15f ",clock() / (double)CLOCKS_PER_SEC) const int N = 100000 + 5; const int M = 5000 + 5; int n,m; char s[N],t[M]; std::bitset<N> bs[3],w[26]; void work() { for (int i = 0; i < 3; ++ i) { bs[i].reset(); } for (int i = 0; i < 26; ++ i) { w[i].reset(); } for (int i = 0; i < n; ++ i) { w[s[i] - 'a'][i] = 1; } for (int i = 0; i < n; ++ i) { bs[0][i] = 1; } for (int i = 0; i < m; ++ i) { int a = t[i] - 'a'; bs[(i + 1) % 3] = bs[i % 3] & w[a] >> i; if (i > 0) { int b = t[i - 1] - 'a'; bs[(i + 1) % 3] |= bs[(i + 2) % 3] & w[a] >> i - 1 & w[b] >> i; } } for (int i = 0; i < n; ++ i) { if (bs[m % 3][i] == 1) putchar('1'); else putchar('0'); } puts(""); } int main() { int cas; scanf("%d",&cas); while (cas--) { scanf("%d%d",&n,&m); scanf("%s%s",s,t); work(); } }
不会 M Memento Mori