2016.6.30模拟赛
T1
【题目来源】Codeforces Beta Round #72 Div1 E
【题解】
题目等价于最大化重叠长度。
最直接的想法,由于重叠部分只和后面$20$个字符有关,用$f(i,s1,s2)$表示考虑了前$i$个字符串,一个序列的最后$20$个点为$s1$,另一个序列最后$20$个点为$s2$.
这样很好转移,但是状态数是$2^{40}n$,无法接受。
我们注意到每个串长度相同,所以$s1,s2$一定是某个字符串,所以可以用$1..n$来代替$s1$, 这样状态数变为$n^3$。
再注意到,$i$这个点一定是某一个序列的结尾,所以只用$f(i,j)$表示即可,其中$j$表示另一个序列的结尾位置。这样状态数变为$n^2$。
状态数已经很优了,我们来优化一下转移。
转移方程:
$$f(i+1,j) = f(i,j) + calc(i,i+1) (j<i)$$
$$f(i+1,i) = \max \{f(i,k) + calc(k, i+1) (k<i) \} $$
第一种转移每个状态加上了相同的量,所以记录一个全局tag即可,第二个转移需要我们花$O(n)$的时间枚举,但我们注意到$calc(k,i+1)$最多只有$20$种可能,可以来枚举这个。当$d = calc(k, i+1)$确定之后,我们就要找一个字符串他的后$d$个字符与$i+1$的前$d$个字符相同,并且它的dp值最大,这样我们就可以维护$g(i,s)$表示后$i$位为$s$前面不定的最大dp值,这样就可以$O(20)$算出第二种转移。更新的时候我们也只需要花$O(20)$的时间去更新f就行了。时间复杂度$O(20n)$
最后,我们需要用$2^{20}$在最终的$g$里枚举一下,这也是可以接受的。
【code】
#include<bits/stdc++.h> using namespace std; const int N = 200000 + 10, INF = 0x3f3f3f3f; int f[21][1 << 20], a[N], n, L; int front(int x, int i) { return x >> (L - i); } int back(int x, int i) { return x & ((1 << i) - 1); } void input() { scanf("%d", &n); char s[99]; for(int i = 0; i < n; i++) { scanf("%s", s); for(int j = 0; s[j]; j++) { (a[i] <<= 1) |= s[j] - '0'; } } L = strlen(s); } int calc(int x, int y) { for(int i = L; i >= 0; i--) { if(back(x, i) == front(y, i)) return i; } abort(); } void maxit(int &x, int y) { if(x < y) x = y; } void solve() { int tag = 0; memset(f, -0x3f, sizeof f); for(int i = 1; i < n; i++) { int res = tag; // [0,i-1]构成子序列 for(int j = 0; j <= L; j++) { maxit(res, j + f[j][front(a[i], j)] + tag); } tag += calc(a[i-1], a[i]); res -= tag; for(int j = 0; j <= L; j++) { maxit(f[j][back(a[i-1], j)], res); } } int ans = 0;//这里来考虑有一个序列为空的情况 for(int i = 0; i < (1 << L); i++) { maxit(ans, f[L][i]); } printf("%d\n", n * L - (ans + tag)); } int main() { freopen("string.in", "r", stdin); freopen("string.out", "w", stdout); input(); solve(); return 0; }
T2
【题目来源】Codeforces Abbyy Cup 2.0-Final B
【题意】
【题解】注意认真看题,能不能传递并不是由$i$决定的,而是由$j$决定的,所以要把$a$反过来读。
然后就很简单了,注意到如果从$s$能够走到$t$,那么对于$\forall s < p < t$,$s$都能够走到$t$。
所以倍长序列以后,求每个点最远能走到哪就可以了。
如果走一步,显然是走到$i + a[i]$这个位置。
如果走两步呢?那么会走到所有$j \in [i,i+a[i]]$里$j+a[j]$最大的点,设为$f(i)$
如果走三步呢?那么会走到所有$j \in [i,i+a[i]]$里$f(j)$最大的点。
于是,这样就可以倍增了。
对于一个起点,可以在$O(n \log n)$的时间里求出走到终点需要几步。
【notice】认真读题。。
【code】
#include<bits/stdc++.h> using namespace std; const int N = 500000 + 10, INF = 1 << 29; int a[N], n, rgt[N]; int f[20][N]; typedef pair<int, int> pii; #define FI first #define SE second struct SegmentTree { pii da[N * 4]; #define mid ((l + r) >> 1) #define ls s << 1, l, mid #define rs s << 1 | 1, mid + 1, r void modify(int s, int l, int r, int p, const pii &x) { if(l == r) return da[s] = x, void(); if(p <= mid) modify(ls, p, x); else modify(rs, p, x); da[s] = max(da[s << 1], da[s << 1 | 1]); } pii query(int s, int l, int r, int L, int R){ if(L <= l && r <= R) return da[s]; pii res(0, 0); if(L <= mid) res = max(res, query(ls, L, R)); if(mid < R) res = max(res, query(rs, L, R)); return res; } } seg; int logn = 19; int calc(int s) { int t = s + n - 1, ans = 2; if(rgt[s] >= t) return 1; for(int i = logn; i >= 0; i--) { if(rgt[f[i][s]] < t) { ans += 1 << i; s = f[i][s]; } } return ans; } int main() { freopen("sequence.in", "r", stdin); freopen("sequence.out", "w", stdout); scanf("%d", &n); for(int i = n; i >= 1; i--) { scanf("%d", a + i); } for(int i = 1; i <= (n << 1); i++) { if(i > n) a[i] = a[i - n]; rgt[i] = min(n << 1, i + a[i]); } for(int i = 1; i <= (n << 1); i++) { seg.modify(1, 1, n << 1, i, pii(rgt[i], i)); } for(int i = 1; i <= (n << 1); i++) { f[0][i] = seg.query(1, 1, n << 1, i, rgt[i]).SE; } for(int i = 1; i <= logn; i++) { for(int j = 1; j <= (n << 1); j++) { f[i][j] = f[i-1][f[i-1][j]]; } } /* for(int i = 0; i <= logn; i++) { printf("%d : ", i); for(int j = 1; j <= (n << 1); j++) { printf("%d%c", f[i][j], " \n"[j == (n<<1)]); } } */ long long ans = 0; for(int i = 1; i <= n; i++) { ans += calc(i); } cout << ans << endl; return 0; }
T3
提交答案题。
旅行商问题,直接贪心走就能A了。。