目录:
一、DP
二、图论
1、最短路
2、强连通分量
三、利用单调性维护
四、贪心
五、数据结构
1、并查集
六、数学
1、计数问题
2、数学分析
七、博弈
八、搜索
//////////////////////////////////
一、DP:
1003
:
(参见 http://hi.baidu.com/aekdycoin/item/88a8be0bf621c6314ac4a3d5 )
然后就是一个区间DP的问题
DP[i][j] 表示从第 i 天到第 j天的最优值,于是方程很显然:
DP[i][j] = min{DP[i][w] + K + DP[w+1][j]} (i<=w
2 Problem: 1003
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:40 ms
7 Memory:1396 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include < string>
13 #include
14 #include
15 #include
16 using namespace std;
17 #define typ long long
18 #define INF 2000000000LL
19 const int N = 110;
20 const int E = 1100;
21 struct Edge {
22 int u, v, nex;
23 typ len;
24 Edge() {
25 }
26 Edge( int _u, int _v, typ _len, int _nex) {
27 u = _u, v = _v, len = _len, nex = _nex;
28 }
29 };
30 queue< int> Q;
31 Edge eg[E];
32 typ dis[N];
33 int g[N], idx;
34 bool vis[N];
35 void addedge( int u, int v, typ len) {
36 eg[idx] = Edge(u, v, len, g[u]);
37 g[u] = idx++;
38 }
39 typ spfa( int key, int st, int ed, int tot) {
40 for ( int i = 1; i <= tot; ++i)
41 dis[i] = INF;
42 memset(vis, 0, sizeof(vis));
43 dis[st] = 0;
44 while (!Q.empty())
45 Q.pop();
46 Q.push(st);
47 while (!Q.empty()) {
48 int x = Q.front();
49 Q.pop();
50 vis[x] = false;
51 for ( int i = g[x]; ~i; i = eg[i].nex) {
52 if (key & ( 1 << eg[i].v))
53 continue;
54 if (eg[i].len + dis[x] < dis[eg[i].v]) {
55 dis[eg[i].v] = dis[x] + eg[i].len;
56 if (!vis[eg[i].v]) {
57 Q.push(eg[i].v);
58 vis[eg[i].v] = true;
59 }
60 }
61 }
62 }
63 return dis[ed];
64 }
65
66 int ar[N], n, m, e;
67 typ kkcld;
68 typ d[N][N];
69
70 typ Cal( int l, int r) {
71 int key = 0;
72 for ( int i = l; i <= r; ++i) {
73 key |= ar[i];
74 }
75 return spfa(key, 1, m, m);
76 }
77 typ dp( int l, int r) {
78 typ &z = d[l][r];
79 if (~z)
80 return z;
81 z = Cal(l, r) * (r - l + 1);
82 for ( int i = l; i + 1 <= r; ++i) {
83 z = min(dp(l, i) + dp(i + 1, r) + kkcld, z);
84 }
85 return z;
86 }
87 void input() {
88 memset(g, - 1, sizeof(g));
89 memset(ar, 0, sizeof(ar));
90 idx = 0;
91 int u, v;
92 typ len;
93 for ( int i = 0; i < e; ++i) {
94 scanf( " %d%d%lld ", &u, &v, &len);
95 addedge(u, v, len);
96 addedge(v, u, len);
97 }
98 int d, p, a, b;
99 scanf( " %d ", &d);
100 for ( int i = 0; i < d; ++i) {
101 scanf( " %d%d%d ", &p, &a, &b);
102 for ( int j = a; j <= b; ++j) {
103 ar[j] |= ( 1 << p);
104 }
105 }
106 }
107 void solve() {
108 memset(d, - 1, sizeof(d));
109 printf( " %lld\n ", dp( 1, n));
110 }
111 int main() {
112 while ( 4 == scanf( " %d%d%lld%d ", &n, &m, &kkcld, &e)) {
113 input();
114 solve();
115 }
116 return 0;
117 }
1009:
KMP预处理+矩阵快速幂加速DP
可以参考( http://hi.baidu.com/wyl8899/item/dc5abdccb571efd597445268 )
f[i, j]代表字符串匹配到第i位时已经匹配了不吉利数字1到j位 时的方案数
KMP预处理状态转移,由于转移方程式都是一样的,可以用矩阵快速幂优化
2 Problem: 1009
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:56 ms
7 Memory:1276 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 using namespace std;
15 const int N = 22;
16 struct Matrix {
17 int r, c;
18 int val[N][N];
19 void clear() {
20 memset(val, 0, sizeof(val));
21 }
22 void One() {
23 for ( int i = 0; i < r; ++i)
24 val[i][i] = 1;
25 }
26 };
27
28 Matrix M, ans;
29 int n, m, mod;
30 int ar[N], f[N];
31
32 Matrix multi(Matrix a, Matrix b) {
33 Matrix re;
34 re.r = a.r, re.c = b.c;
35 re.clear();
36 for ( int i = 0; i < re.r; ++i)
37 for ( int j = 0; j < re.c; ++j)
38 for ( int k = 0; k < re.c; ++k)
39 re.val[i][j] = (re.val[i][j] + (a.val[i][k] * b.val[k][j]) % mod) % mod;
40 return re;
41 }
42 Matrix Pow(Matrix a, int x) {
43 Matrix re;
44 re.clear();
45 re.r = re.c = a.r;
46 re.One();
47 while (x) {
48 if (x & 1) re = multi(re, a);
49 a = multi(a, a);
50 x >>= 1;
51 }
52 return re;
53 }
54 void prepare() {
55 int i, j, p;
56 f[ 0] = f[ 1] = 0;
57 M.r = m, M.c = m;
58 M.clear();
59 for (i = 1; i < m; ++i) {
60 j = f[i];
61 while (j && ar[j] != ar[i]) j = f[j];
62 if (ar[j] == ar[i]) f[i + 1] = j + 1;
63 else f[i + 1] = 0;
64 }
65 for (i = 0; i < m; ++i) {
66 for (j = 0; j < 10; ++j) {
67 p = i;
68 while (p && ar[p] != j) p = f[p];
69 if (ar[p] == j && p == m - 1) continue;
70 if (ar[p] == j) ++M.val[i][p + 1];
71 else ++M.val[i][p];
72 }
73 }
74 ans.r = 1, ans.c = m;
75 ans.clear();
76 ans.val[ 0][ 0] = 1;
77 }
78 int main() {
79 int tot;
80 while ( 3 == scanf( " %d%d%d ", &n, &m, &mod)) {
81 for ( int i = 0; i < m; ++i)
82 scanf( " %1d ", &ar[i]);
83 prepare();
84 tot = 0;
85 M = Pow(M, n);
86 ans = multi(ans, M);
87 for ( int i = 0; i < m; ++i)
88 tot = (tot + ans.val[ 0][i]) % mod;
89 printf( " %d\n ", tot);
90
91 }
92 return 0;
93 }
首先是一个很明显的O(N^2)的dp
dp[i] = dp[j] + min(sum[i] - sum[j] + i - j - 1 + L)^2
dp[i] = min(dp[j] + (sum[i] - sum[j] + i - j - 1 + L)^2)
可以用线段树或者单调队列优化到O(NlogN) ,参见
jsoi2009论文《用单调性优化动规》( http://wenku.baidu.com/view/83e4fec59ec3d5bbfd0a74e1.html )
《1D\1D动态规划初步》 ( http://wenku.baidu.com/view/681d161ca300a6c30c229f70.html )
2 Problem: 1010
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:204 ms
7 Memory:2448 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15 using namespace std;
16 #define typ long long
17 const int N = 50010;
18 struct node {
19 int l, r, id;
20 node() {
21 }
22 node( int _l, int _r, int _id) {
23 l = _l, r = _r, id = _id;
24 }
25 };
26
27 deque
28 typ d[N], sum[N], L, c[N];
29 int n;
30
31 void input() {
32 for ( int i = 1; i <= n; ++i)
33 scanf( " %lld ", &c[i]);
34 sum[ 0] = 0;
35 for ( int i = 1; i <= n; ++i)
36 sum[i] = sum[i - 1] + c[i];
37 }
38 typ sqr(typ x) {
39 return x * x;
40 }
41 typ Cal( int l, int r) {
42 return sqr(sum[r] - sum[l - 1] + r - l - L);
43 }
44 void solve() {
45 node u;
46 while (!Q.empty())
47 Q.pop_back();
48 Q.push_front(node( 1, n, 0));
49 d[ 0] = 0;
50 for ( int i = 1; i <= n; ++i) {
51 u = Q.front();
52 d[i] = d[u.id] + Cal(u.id + 1, i);
53 if (i == n) break;
54 if (Q.front().l < Q.front().r) ++Q.front().l;
55 else Q.pop_front();
56 while ( true) {
57 if (Q.empty()) {
58 Q.push_back(node(i + 1, n, i));
59 break;
60 }
61 u = Q.back();
62 if (d[u.id] + Cal(u.id + 1, u.l) >= d[i] + Cal(i + 1, u.l)) {
63 Q.pop_back();
64 continue;
65 }
66 if (d[u.id] + Cal(u.id + 1, u.r) <= d[i] + Cal(i + 1, u.r)) {
67 if (u.r != n)
68 Q.push_back(node(u.r + 1, n, i));
69 break;
70 }
71 int L = u.l, R = u.r;
72 int mid;
73 while (L < R) {
74 mid = (L + R) >> 1;
75 if (d[u.id] + Cal(u.id + 1, mid) < d[i] + Cal(i + 1, mid)) {
76 L = mid + 1;
77 } else {
78 R = mid;
79 }
80 }
81 Q.back().r = L - 1;
82 Q.push_back(node(L, n, i));
83 break;
84 }
85 }
86 printf( " %lld\n ", d[n]);
87 }
88 int main() {
89 while ( 2 == scanf( " %d%lld ", &n, &L)) {
90 input();
91 solve();
92 }
93 return 0;
94 }
也可以用经典的斜率优化 复杂度是O(N),参见
《从一类单调性问题看算法的优化》 ( http://wenku.baidu.com/view/fa5e7243b307e87101f69683.html )
《数形结合的应用——浅谈动态规划中的斜率优化》
( http://wenku.baidu.com/view/66304ff4ba0d4a7302763ae7.html )
2 #include
3 #include
4 #include
5 using namespace std;
6 #define LL long long
7 const int N = 50010;
8
9 LL d[N], sum[N], L;
10 deque< int> Q;
11 int n;
12
13 void input() {
14 sum[ 0] = 0;
15 for ( int i = 1; i <= n; ++i) {
16 scanf( " %lld ", &sum[i]);
17 sum[i] += sum[i - 1];
18 }
19 }
20 LL A( int x) {
21 return (LL)x + sum[x] - 1LL - L;
22 }
23 LL B( int x) {
24 return (LL)x + sum[x];
25 }
26 LL sqr(LL x) {
27 return x * x;
28 }
29 LL Up( int k1, int k2) {
30 return d[k1] - d[k2] + sqr(B(k1)) - sqr(B(k2)) ;
31 }
32 LL Down( int k1, int k2) {
33 return (B(k1) - B(k2)) * 2LL;
34 }
35 bool g( int k1, int k2, int x) {
36 return Up(k1, k2) <= A(x) * Down(k1, k2);
37 }
38 LL Cal( int i, int j) {
39 return d[i] + sqr(j - i - 1 + sum[j] - sum[i] - L);
40 }
41 void solve() {
42 int u, v;
43 while (!Q.empty())
44 Q.pop_back();
45 d[ 0] = 0;
46 Q.push_back( 0);
47 for ( int i = 1; i <= n; ++i) {
48 while (Q.size() >= 2) {
49 u = Q.front();
50 Q.pop_front();
51 v = Q.front();
52 if (g(u, v, i)) {
53 Q.push_front(u); break;
54 }
55 }
56 u = Q.front();
57 d[i] = Cal(u, i);
58 if (i == n) break;
59 while (Q.size() >= 2) {
60 v = Q.back();
61 Q.pop_back();
62 u = Q.back();
63 if (Up(u, v) * Down(v, i) >= Up(v, i) * Down(u, v)) continue;
64 else {
65 Q.push_back(v); break;
66 }
67 }
68 Q.push_back(i);
69 }
70 printf( " %lld\n ", d[n]);
71 }
72 int main() {
73 while ( 2 == scanf( " %d%lld ", &n, &L)) {
74 input();
75 solve();
76 }
77 return 0;
78 }
1021:
DP: d[i][j][k] //只允许交换前0到i-1种前,使得第一个人拥有j元,第二个人拥有k元 所花费的最少交换次数
然后枚举第i种货币的分配方案,两层循环分别枚举第一个人,第二个人交换完第i种货币后手里还有几枚。
有的题解说要用最大公约数剪枝,我没有试过,总之速度还算行。
2 Problem: 1021
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:472 ms
7 Memory:8776 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13
14 const int INF = ( int)(1e9) + 10;
15
16 const int N = 6;
17 int own[ 3][N];
18
19 int x1, x2, x3;
20 int sum[ 3], mon[N];
21
22 const int MAX = 1000 + 10;
23 int d[ 2][MAX][MAX];
24
25 int val[N] = { 1, 5, 10, 20, 50, 100};
26
27 void update( int& x, int y) {
28 if (- 1 == x)
29 x = y;
30 else
31 x = std::min(x, y);
32 }
33 void work() {
34 memset(mon, 0, sizeof(mon));
35
36 int tot = 0;
37 for ( int i = 0; i < 3; ++i) {
38 sum[i] = 0;
39 for ( int j = N - 1; j >= 0; --j) {
40 scanf( " %d ", &own[i][j]);
41 mon[j] += own[i][j];
42 sum[i] += own[i][j] * val[j];
43 }
44 tot += sum[i];
45 }
46
47 memset(d[ 0], - 1, sizeof(d[ 0]));
48 d[ 0][sum[ 0]][sum[ 1]] = 0;
49 for ( int i = 0; i < N; ++i) {
50 int now = i & 1;
51 memset(d[ 1 - now], - 1, sizeof(d[ 0]));
52
53 for ( int j = 0; j <= tot; ++j) {
54 for ( int k = 0; k + j <= tot; ++k) {
55 if (d[now][j][k] >= 0) {
56 update(d[ 1 - now][j][k], d[now][j][k]);
57
58 for ( int a = 0; a <= mon[i]; ++a) {
59 for ( int b = 0; b + a <= mon[i]; ++b) {
60 int suma = j + val[i] * (a - own[ 0][i]);
61 int sumb = k + val[i] * (b - own[ 1][i]);
62 if (suma >= 0 && sumb >= 0 && suma + sumb <= tot) {
63 int dis = (std::abs(a - own[ 0][i]) + std::abs(b - own[ 1][i]) + std::abs(mon[i] - a - b - own[ 2][i])) / 2;
64 update(d[ 1 - now][suma][sumb], d[now][j][k] + dis);
65 }
66 }
67 }
68 }
69 }
70 }
71 }
72 int ea = sum[ 0], eb = sum[ 1], ec = sum[ 2];
73 ea -= x1; eb += x1;
74 eb -= x2; ec += x2;
75 ec -= x3; ea += x3;
76 if (ea < 0 || eb < 0 || ec < 0 || ea + eb + ec != tot || d[N & 1][ea][eb] < 0)
77 puts( " impossible ");
78 else
79 printf( " %d\n ", d[N & 1][ea][eb]);
80 }
81 int main() {
82 while ( 3 == scanf( " %d%d%d ", &x1, &x2, &x3)) {
83 work();
84 }
85 return 0;
86 }
1025:
(参见 http://www.google.com.hk/url?sa=t&rct=j&q=[SCOI2009]%E6%B8%B8%E6%88%8F&source=web&cd=9&ved=0CFsQFjAI&url=http%3a%2f%2fabyss.ylen.me%2farchives%2f55&ei=Y3QsUsC-Aa6viQfsmoDYDA&usg=AFQjCNEyp1WqBW9xrWIA8i277vs_PMkKGw )
通过分析,我们发现,如果一个数的循环节是x,那么一定有x个数的循环节是x。因为一个数在他的循环中,不可能两次遇到同一个数。
而排列数就是每个循环节的LCM。
于是我们将问题抽象成:将N分割成k个数(k <= N),即(a1+....+ak) = N,问我们LCM(a1, a2, ...., ak)有多少种?
由于1对LCM是没有影响的,所以问题又变成了(a1+a2+....+ak) <= N,那么LCM(a1, a2, ..., ak)有多少种?
我们可以DP解决
d[i][j]//前0到i-1个素数组成和为j时的方案数(j = (pime[0]^C0 + prime[1]*C1 + .... + prime[i - 1]*Ci - 1)
= d[i - 1][j] + sigme{d[i - 1][j - k]};//k = prime[i]^c;(c >= 1)
2 Problem: 1025
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:96 ms
7 Memory:8776 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 typedef long long ll;
15
16 const int N = 1000 + 10;
17 ll d[N][N];
18 bool vis[N];
19
20 int n;
21 std::vector< int> prime;
22
23 void prepare() {
24 prime.clear();
25 memset(vis, 0, sizeof(vis));
26 for ( int i = 2; i <= n; ++i) {
27 if (!vis[i]) {
28 prime.push_back(i);
29 for ( int j = i * i; j <= n; j += i)
30 vis[j] = true;
31 }
32 }
33 }
34 int main() {
35 while ( 1 == scanf( " %d ", &n)) {
36 prepare();
37
38 memset(d, 0, sizeof(d));
39 d[ 0][ 0] = 1;
40 for ( int i = 0; i < prime.size(); ++i) {
41 for ( int j = 0; j <= n; ++j) {
42 if (d[i][j] == 0) continue;
43 d[i + 1][j] += d[i][j];
44 for ( int z = prime[i]; z + j <= n; z *= prime[i]) {
45 d[i + 1][j + z] += d[i][j];
46 }
47 }
48 }
49
50 ll ans = 0;
51 for ( int i = 0; i <= n; ++i)
52 ans += d[prime.size()][i];
53 printf( " %lld\n ", ans);
54 }
55 return 0;
56 }
1026:
数位dp:
d[i][j]//以i做为开头,长度为j的数,可以组成多少个windy数 (注意,当i为0的时候,i不能是前导0)
2 Problem: 1026
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:0 ms
7 Memory:804 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14
15 const int N = 10 + 2;
16 int d[N][N];
17
18 int dp( int x, int len) {
19 if (~d[x][len]) return d[x][len];
20 if ( 1 == len) {
21 return d[x][len] = 1;
22 }
23 d[x][len] = 0;
24 for ( int i = x + 2; i < 10; ++i)
25 d[x][len] += dp(i, len - 1);
26 for ( int i = x - 2; i >= 0; --i) {
27 d[x][len] += dp(i, len - 1);
28 }
29 return d[x][len];
30 }
31 int a, b;
32 std::vector< int>ar;
33
34 void dfs( int& re, int len) {
35 if (len == 1) {
36 int z = 0;
37 for ( int i = ar[len] + 2; i <= ar[len - 1]; ++i)
38 ++re, ++z;
39 for ( int i = std::min(ar[len] - 2, ar[len - 1]); i >= 0; --i)
40 ++re, ++z;
41 } else {
42 for ( int i = std::min(ar[len] - 2, ar[len - 1]); i >= 0; --i) {
43 if (i == ar[len - 1]) {
44 dfs(re, len - 1);
45 } else {
46 re += dp(i, len);
47 }
48 }
49 for ( int i = ar[len] + 2; i <= ar[len - 1]; ++i) {
50 if (i == ar[len - 1]) {
51 dfs(re, len - 1);
52 } else {
53 re += dp(i, len);
54 }
55 }
56 }
57 }
58 int Cal( int x) {
59 if (x < 0) return 0;
60 else if ( 0 == x) return 1;
61
62 ar.clear();
63 int re = 0;
64 while (x) {
65 ar.push_back(x % 10);
66 x /= 10;
67 }
68
69 if ( 1 == ar.size())
70 return ar[ 0] + 1;
71
72 int len = ar.size();
73 for ( int i = 1; i < 10; ++i)
74 for ( int j = 1; j < len; ++j)
75 re += dp(i, j);
76 ++re;
77 for ( int i = 1; i <= ar[len - 1]; ++i) {
78 if (i == ar[len - 1]) {
79 dfs(re, len - 1);
80 } else {
81 re += dp(i, len);
82 }
83 }
84 return re;
85 }
86 void work() {
87 int ansa = Cal(a - 1);
88 int ansb = Cal(b);
89 printf( " %d\n ", ansb - ansa);
90 }
91 int main() {
92 memset(d, - 1, sizeof(d));
93
94 while ( 2 == scanf( " %d%d ", &a, &b)) {
95 work();
96 }
97 return 0;
98 }
1037:
DP:d[i][j][k]/以i-1个数结尾的所有连续断都满足条件,以第i个数结尾的所有连续段中,男生最多比女生多j个,女生最多比男生多k个
d[i][j][k] = d[i - 1][j + 1][k - 1] //第i个是男生
+ d[i - 1][j - 1][k + 1];//第i个是女生
由于内存问题,需要用滚动数组
2 Problem: 1037
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:428 ms
7 Memory:1588 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13
14 const int mod = 12345678;
15
16 int n, m, lim;
17
18 const int N = 20 + 5;
19 const int M = 150 + 10;
20 int d[ 2][M][N][N];
21
22 void work() {
23 memset(d, 0, sizeof(d));
24 d[ 0][ 0][ 0][ 0] = 1;
25
26 for ( int i = 1; i <= (n + m); ++i) {
27 int now = i & 1, pre = 1 - now;
28 memset(d[now], 0, sizeof(d[ 0]));
29
30 for ( int j = 0; j <= std::min(i, n); ++j)
31 for ( int k1 = 0; k1 <= std::min(i, lim); ++k1) {
32 for ( int k2= 0; k2 <= std::min(lim, i - j); ++k2) {
33 int& z = d[now][j + 1][k1 + 1][std::max( 0, k2 - 1)];
34 z = (z + d[pre][j][k1][k2]) % mod;
35 int& zz = d[now][j][std::max( 0, k1 - 1)][k2 + 1];
36 zz = (zz + d[pre][j][k1][k2]) % mod;
37 }
38 }
39 }
40
41 int sum = 0;
42 for ( int k1 = 0; k1 <= lim; ++k1)
43 for ( int k2 = 0; k2 <= lim; ++k2)
44 sum = (sum + d[(n + m) & 1][n][k1][k2]) % mod;
45 printf( " %d\n ", sum);
46 }
47
48 int main() {
49 while ( 3 == scanf( " %d%d%d ", &n, &m, &lim)) {
50 work();
51 }
52 return 0;
53 }
1044:
[HAOI2008]木棍分割第一问是经典的二分题,二分最大长度的最小值,贪心判断
第二问dp做:
d[i,j] //前j个木棍,恰好砍了i刀的方案数
= d[i - 1, k]; //k是所有满足 sum(k + 1, j) <= 第一问的结果的值
我们发现,此处具有决策的单调性,所以我们模拟队列解决
另外由于空间问题需要滚动数组
2 Problem: 1044
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:7056 ms
7 Memory:1588 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 const int mod = 10007;
14 const int N = 50000 + 10;
15
16 int ar[N], sum[N], d[ 2][N];
17
18 int n, m;
19
20 bool judge( int val) {
21 int re = 0, tot = 0;
22 for ( int i = 1; i <= n; ++i) {
23 tot = tot + ar[i];
24 if (tot > val) {
25 ++re;
26 tot = ar[i];
27 }
28 }
29 if (re <= m) return true;
30 else return false;
31 }
32 void work() {
33 sum[ 0] = ar[ 0] = 0;
34 for ( int i = 1; i <= n; ++i) {
35 scanf( " %d ", &ar[i]);
36 sum[i] = sum[i - 1] + ar[i];
37 }
38 int l, r, mid;
39 l = 0; r = sum[n];
40 while (r - l > 1) {
41 mid = (l + r) / 2;
42 if (judge(mid))
43 r = mid;
44 else
45 l = mid;
46 }
47 printf( " %d ", r);
48
49 int ans = r;
50 memset(d[ 0], 0, sizeof(d[ 0]));
51 for ( int i = 1; i <= n; ++i)
52 if (sum[i] <= ans)
53 ++d[ 0][i];
54 else
55 break;
56
57 int idx;
58 int tot, presum = 0, a = d[ 0][n];
59
60 for ( int j = 1; j <= m; ++j) {
61 int now = j & 1, pre = 1 - now;
62 memset(d[now], 0, sizeof(d[now]));
63
64 presum = tot = 0;
65 idx = 1;
66
67 for ( int i = 2; i <= n; ++i) {
68 tot = tot + ar[i];
69 presum = (presum + d[pre][i - 1]) % mod;
70
71 while (tot > ans) {
72 tot = tot - ar[idx + 1];
73 presum = (presum - d[pre][idx] + mod) % mod;
74 ++idx;
75 }
76
77 d[now][i] = presum;
78 }
79 a = (a + d[now][n]) % mod;
80 }
81
82 printf( " %d\n ", a);
83 }
84 int main() {
85 while ( 2 == scanf( " %d%d ", &n, &m)) {
86 work();
87 }
88 return 0;
89 }
1046:
首先倒着做O(NlogN)的LIS,然后深搜
2 Problem: 1046
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:1792 ms
7 Memory:928 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14
15 const int INF = ( int)1e9 + 10;
16 const int N = 10000 + 10;
17 int ar[N], g[N], d[N], n;
18
19 int bsearch( int val) {
20 int l, r, mid;
21 l = 0,r = n;
22 while (r - l > 1) {
23 mid = (l + r) >> 1;
24 if (g[mid] <= val)
25 r = mid;
26 else
27 l = mid;
28 }
29 g[r] = val;
30 return r;
31 }
32 std::vector< int> ans;
33
34 void work() {
35 for ( int i = 1; i <= n; ++i)
36 scanf( " %d ", &ar[i]);
37 std::fill(g, g + n + 2, -INF);
38 int mx = 0;
39 for ( int i = 1; i <= n; ++i) {
40 d[n - i + 1] = bsearch(ar[n - i + 1]);
41 mx = std::max(mx, d[n + 1 - i]);
42 }
43
44 int Q, v;
45 scanf( " %d ", &Q);
46 while (Q --) {
47 scanf( " %d ", &v);
48 if (v > mx)
49 puts( " Impossible ");
50 else {
51 ans.clear();
52 ans.push_back(-INF);
53 for ( int i = 1; i <= n; ++i) {
54 if (d[i] >= v && ar[i] > ans[ans.size() - 1]) {
55 ans.push_back(ar[i]);
56 if (--v == 0) break;
57 }
58 }
59 for ( int i = 1; i < ans.size(); ++i) {
60 if (i > 1)
61 putchar( ' ');
62 printf( " %d ", ans[i]);
63 }
64 putchar( ' \n ');
65 }
66 }
67 }
68 int main() {
69 while ( 1 == scanf( " %d ", &n)) {
70 work();
71 }
72 return 0;
73 }
1048:
陈题了,黑书上DP那章有介绍, 要求均方差最小,可以先将公式变形,发现只要各部分平方和最大即可
2 Problem: 1048
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:104 ms
7 Memory:1788 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 const int INF = ( int)(1e9) + 10;
15
16 const int N = 10 + 2;
17 int d[N][N][N][N][N]; // x1,y1,x2,y2,lim
18
19 int r, c, lim;
20
21 int ar[N][N];
22 int sum( int x1, int y1, int x2, int y2) {
23 int re = 0;
24 for ( int i = x1; i <= x2; ++i)
25 for ( int j = y1; j <= y2; ++j)
26 re += ar[i][j];
27 return re * re;
28 }
29 int dp( int x1, int y1, int x2, int y2, int ti) {
30 int& z = d[x1][y1][x2][y2][ti];
31 if (~z) return z;
32 if ( 1 == ti) {
33 return z = sum(x1, y1, x2, y2);
34 }
35 z = INF;
36 for ( int i = x1; i < x2; ++i)
37 for ( int j = 1; j < ti; ++j)
38 z = std::min(z, dp(x1, y1, i, y2, j) + dp(i + 1, y1, x2, y2, ti - j));
39 for ( int i = y1; i < y2; ++i) {
40 for ( int j = 1; j < ti; ++j)
41 z = std::min(z, dp(x1, y1, x2, i, j) + dp(x1, i + 1, x2, y2, ti - j));
42 }
43 return z;
44 }
45 void work() {
46 int tot = 0;
47 for ( int i = 1; i <= r; ++i)
48 for ( int j = 1; j <= c; ++j) {
49 scanf( " %d ", &ar[i][j]);
50 tot += ar[i][j];
51 }
52 memset(d, - 1, sizeof(d));
53
54 int mx = dp( 1, 1, r, c, lim);
55 // printf("%d\n", mx);
56
57 double avg = ( double)tot / lim;
58 double ans = std::sqrt(( double)mx / lim - 2. * avg * tot / lim + avg * avg);
59 printf( " %.2f\n ", ans);
60 }
61 int main() {
62 while ( 3 == scanf( " %d%d%d ", &r, &c, &lim)) {
63 work();
64 }
65 return 0;
66 }
1049:
我们要修改最少的数让原序列变成一个严格单调上升序列(不重复)。
我们可以先读入每一个数,然后ar[i] - i后,问题转化成为修改最少的数让原序列变成非严格单调上升序列(可重复),
由此我们可以知道,最终序列中的每一个数在原序列都出现过。我们可以将原序列的数提取出来,离散化,然后做dp。
f[i,j]//前i个数已经非严格单调增,且第i位的值<=j,
f[i,j] = min(f[i, j - 1], f[i - 1, j - 1] + cost); //枚举第i位的值
2 Problem: 1049
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:5108 ms
7 Memory:1352 kb
8 *************************************************************** */
9
10 #include
11 #include
12 struct node {
13 int val, cos;
14 };
15
16 const int N = 35000 + 3;
17 node d[N];
18
19 int n;
20 int ar[N], x[N];
21
22 inline void update(node& a, int& nval, int& ncos) {
23 if (a.val >= 0) {
24 if (nval < a.val) {
25 a.val = nval;
26 a.cos = ncos;
27 } else if (nval == a.val) {
28 a.cos = std::min(a.cos, ncos);
29 }
30 } else {
31 a.val = nval;
32 a.cos = ncos;
33 }
34 }
35 void work() {
36 for ( int i = 1; i <= n; ++i) {
37 scanf( " %d ", &ar[i]);
38 ar[i] = ar[i] - i;
39 x[i - 1] = ar[i];
40 }
41
42 std::sort(x, x + n);
43 int idx = std::unique(x, x + n) - x;
44
45 for ( int i = 0; i < idx; ++i)
46 d[i].cos = d[i].val = 0;
47
48 for ( int i = 1; i <= n; ++i) {
49 for ( int j = 0; j < idx; ++j) {
50 if (d[j].val >= 0) {
51 if (ar[i] != x[j])
52 d[j].val = d[j].val + 1;
53 d[j].cos = d[j].cos + std::abs(ar[i] - x[j]);
54 }
55 if (j > 0 && d[j - 1].val >= 0) {
56 update(d[j], d[j - 1].val, d[j - 1].cos);
57 }
58 // printf("%d %d %d %d\n", i, j, d[now][j].val, d[now][j].cos);
59 }
60 }
61
62 node ans;
63 ans.val = - 1; ans.cos = 0;
64 for ( int i = 0; i < idx; ++i)
65 if (d[i].val >= 0)
66 update(ans, d[i].val, d[i].cos);
67 printf( " %d\n%d\n ", ans.val, ans.cos);
68 }
69 int main() {
70 while ( 1 == scanf( " %d ", &n)) {
71 work();
72 }
73 return 0;
74 }
1072:
c++用STL中next_permutation枚举,set判重,可以直接过,但是速度不够快。
2 Problem: 1072
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:4320 ms
7 Memory:7804 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include < set>
13 #include
14 typedef long long ll;
15 const int N = 10 + 3;
16
17 std:: set
18
19 char s[N];
20 int ar[N];
21
22 void work() {
23 int d;
24 v.clear();
25
26 scanf( " %s%d ", s, &d);
27
28 int idx = 0;
29 for ( int i = 0; s[i]; ++i) {
30 ar[idx ++] = s[i] - ' 0 ';
31 }
32
33 ll ans = 0;
34
35 std::sort(ar, ar + idx);
36 do {
37 ll num = 0;
38 for ( int i = 0; i < idx; ++i) {
39 num = num * 10 + ar[i];
40 }
41 if (v.count(num)) continue;
42 else if (num % d == 0) {
43 ++ans;
44 v.insert(num);
45 }
46 } while (std::next_permutation(ar, ar + idx));
47
48 printf( " %lld\n ", ans);
49 }
50 int main() {
51 int T;
52 scanf( " %d ", &T);
53 while (T -- > 0) {
54 work();
55 }
56 return 0;
57 }
比较好的方法是状压DP,
d[s][i] //集合s表示已经被取走了的数的集合,i表示这些数可以有多少种组成方式使得组成的数mod d = i
我们有转移方程
d[s | (1 << ar[k])][(i * 10 + ar[k]) % d] += d[s][i]; //s中没有第k个数
2 Problem: 1072
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:824 ms
7 Memory:8828 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 typedef long long ll;
14 const int N = 1000 + 2;
15
16 char ch[N];
17 int ar[N];
18
19 ll dp[ 1 << 10][N];
20
21 void work() {
22 int d;
23 scanf( " %s%d ", ch, &d);
24
25 int idx = 0;
26 for ( int i = 0; ch[i]; ++i) {
27 ar[idx ++] = ch[i] - ' 0 ';
28 }
29
30 ll ans = 0, tmp = 1;
31 std::sort(ar, ar + idx);
32
33 for ( int i = 0; i < idx;) {
34 int cnt = 0;
35 for ( int j = i; j < idx; ++j) {
36 if (ar[j] == ar[i]) ++cnt;
37 else {
38 i = j; break;
39 }
40 if (j + 1 == idx) i = idx;
41 }
42 for ( int j = 2; j <= cnt; ++j) {
43 tmp = tmp * j;
44 }
45 }
46
47 memset(dp, 0, sizeof(dp));
48 dp[ 0][ 0] = 1;
49
50 int mx = 1 << idx;
51 for ( int i = 0; i < idx; ++i) {
52 if (i == 0) {
53 for ( int k = 0; k < idx; ++k)
54 dp[ 1 << k][ar[k] % d] = 1;
55
56 } else {
57 int s = ( 1 << i) - 1;
58 while (s < mx) {
59 for ( int j = 0; j < d; ++j) {
60 if (dp[s][j] > 0)
61 for ( int k = 0; k < idx; ++k) {
62 if (!(s >> k & 1)) {
63 dp[s | ( 1 << k)][(j * 10 + ar[k]) % d] += dp[s][j];
64 }
65 }
66 }
67 int x = s & -s;
68 int y = s + x;
69 s = ((s & ~y) / x >> 1) | y;
70 }
71 }
72 }
73 printf( " %lld\n ", dp[mx - 1][ 0] / tmp);
74 }
75 int main() {
76 int T;
77 scanf( " %d ", &T);
78 while (T -- > 0) {
79 work();
80 }
81 return 0;
82 }
1084:
我们注意到列最大只有2,所以分开讨论,当列只有1的时候
d[i, j]//已经取完前i行,累计取了j个矩阵
-> d[k,j] //第i + 1行到第k行不取
-> d[k,j + 1]//取第i+1到第k行的矩阵
列为2的时候差不多,d[i,j,k]//第一列取到了第i行,第2列取到了第j行,累计取了k个矩阵
2 Problem: 1084
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:1548 ms
7 Memory:1352 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13
14 const int INF = ( int)(1e9) + 10;
15
16 const int R = 100 + 3;
17 const int C = 2 + 3;
18 const int G = 10 + 3;
19 int d[R][R][G];
20 int rd[R][G];
21
22 int ar[R][C];
23
24 int r, c, lim;
25
26 int sum( int x1, int y1, int x2, int y2) {
27 int re = 0;
28 for ( int i = x1; i <= x2; ++i) {
29 for ( int j = y1; j <= y2; ++j) {
30 re += ar[i][j];
31 }
32 }
33 return re;
34 }
35 void update( int& z, int v) {
36 if (-INF == z)
37 z = v;
38 else
39 z = std::max(z, v);
40 }
41 void work() {
42 for ( int i = 1; i <= r; ++i)
43 for ( int j = 1; j <= c; ++j)
44 scanf( " %d ", &ar[i][j]);
45
46 int ans = - INF;
47 if ( 1 == c) {
48 for ( int i = 0; i <= r; ++i)
49 for ( int j = 0; j <= lim; ++j)
50 rd[i][j] = -INF;
51 rd[ 0][ 0] = 0;
52 for ( int i = 0; i < r; ++i) {
53 for ( int j = 0; j <= lim; ++j) {
54 if (rd[i][j] == -INF) continue;
55 for ( int k = i + 1; k <= r; ++k) {
56 update(rd[k][j], rd[i][j]);
57 update(rd[k][j + 1], rd[i][j] + sum(i + 1, 1, k, 1));
58 if (j + 1 == lim) update(ans, rd[k][j + 1]);
59 }
60 }
61 }
62 } else {
63 for ( int i = 0; i <= r; ++i)
64 for ( int j = 0; j <= r; ++j)
65 for ( int k = 0; k <= lim; ++k)
66 d[i][j][k] = -INF;
67
68 d[ 0][ 0][ 0] = 0;
69 for ( int i = 0; i <= r; ++i)
70 for ( int j = 0; j <= r; ++j) {
71 for ( int k = 0; k <= lim; ++k) {
72 if (d[i][j][k] == -INF) continue;
73 int z = std::max(i + 1, j + 1);
74 for ( int to = z; to <= r; ++to) {
75 update(d[to][to][k], d[i][j][k]);
76 update(d[to][to][k + 1], d[i][j][k] + sum(z, 1, to, 2));
77 if (k + 1 == lim) update(ans, d[to][to][k + 1]);
78 }
79 for ( int to = i + 1; to <= r; ++to) {
80 update(d[to][j][k], d[i][j][k]);
81 update(d[to][j][k + 1], d[i][j][k] + sum(i + 1, 1, to, 1));
82 if (k + 1 == lim) update(ans, d[to][j][k + 1]);
83 }
84 for ( int to = j + 1; to <= r; ++to) {
85 update(d[i][to][k], d[i][j][k]);
86 update(d[i][to][k + 1], d[i][j][k] + sum(j + 1, 2, to, 2));
87 if (k + 1 == lim) update(ans, d[i][to][k + 1]);
88 }
89 }
90 }
91 }
92
93 if ( 0 == lim) ans = 0;
94
95 printf( " %d\n ", ans);
96 }
97 int main() {
98 while ( 3 == scanf( " %d%d%d ", &r, &c, &lim)) {
99 work();
100 }
101 return 0;
102 }
/////////////////////////////////////
二、图论
1、最短路
1001:
[BeiJing2006]狼抓兔子
点太多,网络流会T掉。利用平面图性质转换成偶图,然后将网络流转换成最短路,参见周冬论文 《两极相通——浅谈最大最小定理在信息学竞赛中的应用》
(链接君 http://wenku.baidu.com/view/f8239d1910a6f524ccbf85ce.html )
2 Problem: 1001
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:3772 ms
7 Memory:120416 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15 using namespace std;
16 #define typ int
17 #define INF 1000000000
18 const int N = 2000005;
19 const int E = N * 3;
20 struct Edge {
21 int u, v, nex;
22 typ len;
23 };
24
25 int Q[N], head, tail;
26 Edge eg[E];
27 typ dis[N];
28 int g[N], idx, n, m, st, ed;
29 bool vis[N];
30
31 void addedge( int u, int v, typ len) {
32 eg[idx].u = u, eg[idx].v = v, eg[idx].len = len, eg[idx].nex = g[u];
33 g[u] = idx++;
34 }
35 typ spfa( int st, int ed, int tot) {
36 for ( int i = 1; i <= tot; ++i)
37 dis[i] = INF;
38 memset(vis, 0, sizeof(vis));
39 head = tail = 0;
40 dis[st] = 0;
41 Q[tail++] = st;
42 while (head != tail) {
43 int x = Q[head++];
44 if (head == N) head = 0;
45 vis[x] = false;
46 for ( int i = g[x]; ~i; i = eg[i].nex) {
47 if (eg[i].len + dis[x] < dis[eg[i].v]) {
48 dis[eg[i].v] = dis[x] + eg[i].len;
49 if (!vis[eg[i].v]) {
50 Q[tail++] = eg[i].v;
51 if (tail == N) tail = 0;
52 vis[eg[i].v] = true;
53 }
54 }
55 }
56 }
57 return dis[ed];
58 }
59 void input() {
60 int id, nid, len;
61 memset(g, - 1, sizeof(g));
62 idx = 0;
63 st = 0; ed = (n - 2) * 2 * m + (m - 1) * 2 + 1;
64 for ( int i = 1; i <= n; ++i) {
65 for ( int j = 1; j <= m - 1; ++j) {
66 id = m * 2 * (i - 1) + j * 2;
67 nid = m * 2 * (i - 2) + j * 2 - 1;
68 scanf( " %d ", &len);
69 if ( 1 == i) {
70 addedge(ed, id, len);
71 addedge(id, ed, len);
72 } else if (i == n) {
73 addedge(nid, st, len);
74 addedge(st, nid, len);
75 } else {
76 addedge(id, nid, len);
77 addedge(nid, id, len);
78 }
79 }
80 }
81 for ( int i = 1; i <= n - 1; ++i)
82 for ( int j = 1; j <= m; ++j) {
83 scanf( " %d ", &len);
84 id = m * 2 * (i - 1) + j * 2 - 1;
85 nid = m * 2 * (i - 1) + (j - 1) * 2;
86 if ( 1 == j) {
87 addedge(st, id, len);
88 addedge(id, st, len);
89 } else if (m == j) {
90 addedge(nid, ed, len);
91 addedge(ed, nid, len);
92 } else {
93 addedge(id, nid, len);
94 addedge(nid, id, len);
95 }
96 }
97 for ( int i = 1; i <= n - 1; ++i)
98 for ( int j = 1; j <= m - 1; ++j) {
99 scanf( " %d ", &len);
100 id = m * 2 * (i - 1) + j * 2;
101 nid = id - 1;
102 addedge(id, nid, len);
103 addedge(nid, id, len);
104 }
105 }
106 void solve() {
107 int ans = spfa(st, ed, ed);
108 printf( " %d\n ", ans);
109 }
110 int main() {
111 int u, mx;
112 while ( 2 == scanf( " %d%d ", &n, &m)) {
113 if ( 1 == n && 1 == m) puts( " 0 ");
114 else if ( 1 == n) {
115 mx = INF;
116 for ( int i = 1; i <= m - 1; ++i) {
117 scanf( " %d ", &u);
118 mx = min(u, mx);
119 }
120 printf( " %d ", mx); continue;
121 } else if ( 1 == m) {
122 mx = INF;
123 for ( int i = 1; i <= n - 1; ++i) {
124 scanf( " %d ", &u);
125 mx = min(mx, u);
126 }
127 printf( " %d ", mx); continue;
128 }
129 input();
130 solve();
131 }
132 return 0;
133 }
1050:
枚举最小的边,对最短路变形暴力跑一遍。if (max(d[i], w[i, j]) < d[j]) d[j] = max(d[i], w[i, j]);
2 Problem: 1050
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:4128 ms
7 Memory:996 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15
16 typedef std::pair< int, int>pii;
17 struct Edge {
18 int v, nex, len;
19 Edge(){}
20 Edge( int _v, int _len, int _nex) {
21 nex = _nex, len = _len, v = _v;
22 }
23 };
24
25 const int INF = 30000 + 10;
26 const int N = 500 + 10;
27 const int E = 10000 + 10;
28
29 std::vector< int>prime;
30 bool vv[ 30000 + 10];
31
32 void prepare() {
33 memset(vv, 0, sizeof(vv));
34 for ( int i = 2; i <= 30000; ++i) {
35 if (!vv[i]) {
36 prime.push_back(i);
37 for ( int j = i * i; j <= 30000; j += i)
38 vv[j] = true;
39 }
40 }
41 }
42
43 int g[N], idx;
44 Edge eg[E];
45 void addedge( int u, int v, int len) {
46 eg[idx] = Edge(v, len, g[u]);
47 g[u] = idx++;
48 }
49
50 int dis[N];
51 bool vis[N];
52 int n, m;
53 pii Cal( int val, int s, int t) {
54 std::fill(dis, dis + n + 2, INF);
55 memset(vis, 0, sizeof(vis));
56
57 dis[s] = 0;
58 std::queue< int> Q;
59 Q.push(s);
60
61 while (!Q.empty()) {
62 int u = Q.front(); Q.pop();
63 vis[u] = false;
64 for ( int i = g[u]; ~i; i = eg[i].nex) {
65 if (eg[i].len < val) continue;
66 int l = std::max(dis[u], eg[i].len);
67 int to = eg[i].v;
68 if (l < dis[to]) {
69 dis[to] = l;
70 if (!vis[to]) {
71 vis[to] = true;
72 Q.push(to);
73 }
74 }
75 }
76 }
77 if (dis[t] == INF) return pii(INF, 1);
78 else {
79 pii re(dis[t], val);
80 for ( int i = 0; i < prime.size(); ++i) {
81 while ( 0 == re.first % prime[i] && 0 == re.second % prime[i]) {
82 re.first /= prime[i];
83 re.second /= prime[i];
84 }
85 }
86 return re;
87 }
88 }
89
90 pii Min(pii a, pii b) {
91 if (a.first * b.second < b.first * a.second)
92 return a;
93 else return b;
94 }
95 int x[N];
96 void work() {
97 memset(g, - 1, sizeof(g));
98 idx = 0;
99
100 int idy = 0;
101 int u, v, len;
102 for ( int i = 0; i < m; ++i) {
103 scanf( " %d%d%d ", &u, &v, &len);
104 addedge(u, v, len);
105 addedge(v, u, len);
106 x[idy ++] = len;
107 }
108
109 std::sort(x, x + idy);
110 idy = std::unique(x, x + idy) - x;
111
112 int s, t;
113 scanf( " %d%d ", &s, &t);
114
115 pii ans(INF, 1);
116 for ( int i = 0; i < idy; ++i)
117 ans = Min(ans, Cal(x[i], s, t));
118
119 if (ans.first == INF) {
120 puts( " IMPOSSIBLE ");
121 } else {
122 if (ans.second == 1)
123 printf( " %d\n ", ans.first);
124 else
125 printf( " %d/%d\n ", ans.first, ans.second);
126 }
127 }
128 int main() {
129 prepare();
130 while ( 2 == scanf( " %d%d ", &n, &m)) {
131 work();
132 }
133 return 0;
134 }
2、强连通分量
1051:
先求强连通块,然后重构图,统计出度为0的块的个数,如果为1,输出那一块的大小,如果大于一输出零
2 Problem: 1051
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:80 ms
7 Memory:1928 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15 #include
16 using namespace std;
17 #define INF 1000000000;
18 const int N = 10010;
19 const int E = 50010;
20 struct find_gcc {
21 struct Edge {
22 int v, nex;
23 };
24 Edge eg[E];
25 int g[N], idx;
26 int pre[N], lowlink[N], dfs_clock, scc_cnt;
27 int sccno[N], S[N], top;
28 void dfs( int u) {
29 int v;
30 pre[u] = lowlink[u] = ++dfs_clock;
31 S[top++] = u;
32 for ( int i = g[u]; ~i; i = eg[i].nex) {
33 v = eg[i].v;
34 if (!pre[v]) {
35 dfs(v);
36 lowlink[u] = min(lowlink[u], lowlink[v]);
37 } else if (!sccno[v]) {
38 lowlink[u] = min(lowlink[u], pre[v]);
39 }
40 }
41 if (lowlink[u] == pre[u]) {
42 ++scc_cnt;
43 while (top != 0) {
44 int x = S[top - 1];
45 --top;
46 sccno[x] = scc_cnt;
47 if (x == u)
48 break;
49 }
50 }
51 }
52 void find( int n) {
53 memset(sccno, 0, sizeof(sccno));
54 memset(pre, 0, sizeof(pre));
55 scc_cnt = dfs_clock = top = 0;
56 for ( int i = 0; i < n; ++i) {
57 if (!pre[i])
58 dfs(i);
59 }
60 }
61 void addedge( int u, int v) {
62 eg[idx].v = v, eg[idx].nex = g[u];
63 g[u] = idx++;
64 }
65 void clear( int n) {
66 memset(g, - 1, sizeof(g));
67 idx = 0;
68 }
69 } sol;
70 int n, m;
71 bool key[N];
72 void input() {
73 sol.clear(n);
74 int u, v;
75 for ( int i = 0; i < m; ++i) {
76 scanf( " %d%d ", &u, &v);
77 --u, --v;
78 sol.addedge(u, v);
79 }
80 }
81 void solve() {
82 sol.find(n);
83 if ( 1 == sol.scc_cnt) {
84 printf( " %d\n ", n);
85 } else {
86 memset(key, 0, sizeof(key));
87 for ( int i = 0; i < n; ++i)
88 for ( int j = sol.g[i]; ~j; j = sol.eg[j].nex) {
89 int v = sol.eg[j].v;
90 if (sol.sccno[v] != sol.sccno[i])
91 key[sol.sccno[i]] = true;
92 }
93 int cnt = 0, idx;
94 for ( int i = 1; i <= sol.scc_cnt; ++i) {
95 if (!key[i]) {
96 idx = i;
97 ++cnt;
98 }
99 }
100 if (cnt == 1) {
101 cnt = 0;
102 for ( int i = 0; i < n; ++i)
103 if (sol.sccno[i] == idx) ++cnt;
104 printf( " %d\n ", cnt);
105 } else puts( " 0 ");
106 }
107 }
108 int main() {
109 while ( 2 == scanf( " %d%d ", &n, &m)) {
110 input();
111 solve();
112 }
113 return 0;
114 }
///////////////////////////////////////
三、利用单调性维护
1012:
可以用单调栈来做,维护一个单调增的栈(如果Ai > Aj 并且 i > j, 那么Ai 总是比 Aj 更加有可能成为答案),然后每次二分查找答案。
当然,更加暴力的方法是直接线段树搞。
2 Problem: 1012
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:452 ms
7 Memory:2836 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14
15 const int N = 200010;
16 int sta[N], ar[N], c, cc, m, d;
17 char s[ 10];
18 int bSearch( int L, int deep) {
19 int l = 0, r = c - 1, mid;
20 while (l < r) {
21 mid = (l + r) >> 1;
22 if (sta[mid] < deep - L + 1) {
23 l = mid + 1;
24 } else {
25 r = mid;
26 }
27 }
28 return ar[sta[l]];
29 }
30 int main() {
31 int u, t;
32 while ( 2 == scanf( " %d%d ", &m, &d)) {
33 c = cc = t = 0;
34 for ( int i = 1; i <= m; ++i) {
35 scanf( " %s%d ", s, &u);
36 if (*s == ' A ') {
37 u = (u + t) % d;
38 ar[++cc] = u;
39 while (c != 0 && ar[sta[c - 1]] < u) --c;
40 sta[c ++] = cc;
41 } else if (*s == ' Q ') {
42 t = bSearch(u, cc);
43 printf( " %d\n ", t);
44 }
45 }
46 }
47 return 0;
48 }
1047:
要在一个1000*1000的矩形中找到一个正方形,使得这个正方形中的最大值-最小值最小。
首先我们可以用单调队列维护
rmax[a][b]//从(a,b)这个点向右连续n个点的最大值
rmin[a][b]//从(a,b)这个点向右连续n个点的最小值
复杂度是O(A*B)
然后
qmax[a][b]//以(a,b)为左上角的矩形中的最大值
就等于max(rmax[a][b], .....rmax[a + x - 1][b]);
那么我们就将二维问题变成了一维问题,这个也用单调队列扫一遍就好了
2 Problem: 1047
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:2356 ms
7 Memory:20764 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14
15 struct node {
16 int x, y, v;
17 node( int _x, int _y, int _v) {
18 x = _x, y = _y, v = _v;
19 }
20 node() {}
21 };
22 std::deque
23
24 const int N = 1000 + 10;
25
26 int ar[N][N];
27 int r, c, s;
28
29 int ans;
30 int rmax[N][N], rmin[N][N];
31 int qmax[N][N], qmin[N][N];
32
33 void update_max( int& z, int v) {
34 if (- 1 == z) {
35 z = v;
36 } else {
37 z = std::max(z, v);
38 }
39 }
40 void update_min( int& z, int v) {
41 if (- 1 == z) {
42 z = v;
43 } else {
44 z = std::min(z, v);
45 }
46 }
47 void prepare() {
48 ans = ( int)(1e9) + 10;
49 ///////////////////////////////// /
50 memset(rmax, - 1, sizeof(rmax));
51 memset(rmin, - 1, sizeof(rmin));
52 for ( int i = 1; i <= r; ++i) {
53 while (!Max.empty()) Max.pop_back();
54 while (!Min.empty()) Min.pop_back();
55
56 for ( int j = 1; j < s; ++j) {
57 while (!Max.empty() && Max.back().v < ar[i][j]) {
58 Max.pop_back();
59 }
60 Max.push_back(node(i, j, ar[i][j]));
61 while (!Min.empty() && Min.back().v > ar[i][j]) {
62 Min.pop_back();
63 }
64 Min.push_back(node(i, j, ar[i][j]));
65 }
66
67 for ( int j = s; j <= c; ++j) {
68 while (!Max.empty() && Max.back().v < ar[i][j]) {
69 Max.pop_back();
70 }
71 Max.push_back(node(i, j, ar[i][j]));
72 while (!Min.empty() && Min.back().v > ar[i][j]) {
73 Min.pop_back();
74 }
75 Min.push_back(node(i, j, ar[i][j]));
76 while (!Max.empty() && Max.front().y <= j - s)
77 Max.pop_front();
78 while (!Min.empty() && Min.front().y <= j - s)
79 Min.pop_front();
80
81 update_max(rmax[i][j - s + 1], Max.front().v);
82 update_min(rmin[i][j - s + 1], Min.front().v);
83 // printf("%d*****%d\n", Max.front().v, rmax[i][j - s + 1]);
84 }
85 }
86
87 memset(qmax, - 1, sizeof(qmax));
88 memset(qmin, - 1, sizeof(qmin));
89
90 for ( int j = 1; j + s - 1 <= c; ++j) {
91 while (!Max.empty()) Max.pop_back();
92 while (!Min.empty()) Min.pop_back();
93
94 for ( int i = 1; i < s; ++i) {
95 while (!Max.empty() && Max.back().v < rmax[i][j]) {
96 Max.pop_back();
97 }
98 Max.push_back(node(i, j, rmax[i][j]));
99 while (!Min.empty() && Min.back().v > rmin[i][j]) {
100 Min.pop_back();
101 }
102 Min.push_back(node(i, j, rmin[i][j]));
103 }
104
105 for ( int i = s; i <= r; ++i) {
106 while (!Max.empty() && Max.back().v < rmax[i][j]) {
107 Max.pop_back();
108 }
109 Max.push_back(node(i, j, rmax[i][j]));
110 while (!Min.empty() && Min.back().v > rmin[i][j]) {
111 Min.pop_back();
112 }
113 Min.push_back(node(i, j, rmin[i][j]));
114 while (!Max.empty() && Max.front().x <= i - s)
115 Max.pop_front();
116 while (!Min.empty() && Min.front().x <= i - s)
117 Min.pop_front();
118 update_max(qmax[i - s + 1][j], Max.front().v);
119 update_min(qmin[i - s + 1][j], Min.front().v);
120 }
121 }
122 }
123 void work() {
124 for ( int i = 1; i <= r; ++i)
125 for ( int j = 1; j <= c; ++j)
126 scanf( " %d ", &ar[i][j]);
127
128 prepare();
129
130 for ( int i = 1; i + s - 1 <= r; ++i) {
131 for ( int j = 1; j + s - 1 <= c; ++j) {
132 // printf("%d %d\n", qmax[i][j], qmin[i][j]);
133 ans = std::min(ans, qmax[i][j] - qmin[i][j]);
134 }
135 }
136 printf( " %d\n ", ans);
137 }
138
139 int main() {
140 while ( 3 == scanf( " %d%d%d ", &r, &c, &s)) {
141 work();
142 }
143 return 0;
144 }
/////////////////////////////////////////
四、贪心
1029:
参见论文《浅谈信息学竞赛中的区间问题》(链接
首先我们将所有事件按照结束时间从小到大排序。//理由很显然吧
然后就是一个分阶段的问题,假设当前已经处理完了前i - 1个事件,取了k个事件,总时间是d[i],如果此时还可以选取事件i,那么最优方案显然是选取事件i。
如果不能选取事件i,那么我们已经无法增大k了,但是我们还有可能减小d[i],如果第i个事件解决所需要的时间小于已经取了的k个事件的最大值,那么可以用第i个事件替换那个事件。
用一个优先队列维护就好了。
2 Problem: 1029
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:416 ms
7 Memory:2752 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13
14 typedef std::pair< int, int>pii;
15 const int N = 150000 + 10;
16 pii ar[N];
17
18 std::priority_queue
19 int n;
20
21 bool cmp( const pii& a, const pii& b) {
22 return a.second < b.second;
23 }
24 void work() {
25 while (!Q.empty()) Q.pop();
26
27 for ( int i = 0; i < n; ++i)
28 scanf( " %d%d ", &ar[i].first, &ar[i].second);
29
30 std::sort(ar, ar + n, cmp);
31
32 int now = 0;
33 for ( int i = 0; i < n; ++i) {
34 if (now + ar[i].first <= ar[i].second) {
35 Q.push(ar[i]);
36 now += ar[i].first;
37 } else if (!Q.empty() && Q.top().first >= ar[i].first) {
38 now = now + ar[i].first - Q.top().first;
39 Q.pop();
40 Q.push(ar[i]);
41 }
42 }
43
44 printf( " %d\n ", Q.size());
45 }
46 int main() {
47 while ( 1 == scanf( " %d ", &n)) {
48 work();
49 }
50 return 0;
51 }
1034:
经典的田忌赛马。
参加( http://oibh.info/archives/261 )
2 Problem: 1034
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:252 ms
7 Memory:1588 kb
8 *************************************************************** */
9
10 #include
11 #include
12
13 const int N = 100000 + 10;
14 int a[N], b[N];
15
16 int n;
17
18 int solve( int* a, int* b) {
19 int re = 0;
20 int al, ar, bl, br;
21 al = bl = 0;
22 ar = br = n - 1;
23 while (ar >= al) {
24 if (a[ar] > b[br]) {
25 --ar; --br;
26 re += 2;
27 } else if (a[al] > b[bl]) {
28 ++al; ++bl;
29 re += 2;
30 } else if (a[al] == b[br]) {
31 ++al; --br;
32 re += 1;
33 } else {
34 ++al; --br;
35 }
36 }
37 return re;
38 }
39 void work() {
40 for ( int i = 0; i < n; ++i)
41 scanf( " %d ", &a[i]);
42 for ( int j = 0; j < n; ++j)
43 scanf( " %d ", &b[j]);
44
45 std::sort(a, a + n);
46 std::sort(b, b + n);
47
48 printf( " %d %d\n ", solve(a, b), 2 * n - solve(b, a));
49 }
50 int main() {
51 while ( 1 == scanf( " %d ", &n)) {
52 work();
53 }
54 return 0;
55 }
///////////////////////////////////
五、数据结构
1、并查集
1015:
倒着做并查集
2 Problem: 1015
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:1500 ms
7 Memory:12600 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15 using namespace std;
16 const int M = 200020;
17 const int N = M << 1;
18 struct Edge {
19 int u, v, nex;
20 };
21 int fa[N], cnt, n, m;
22 int g[N], idx, ar[N], c, ans[N];
23 Edge eg[N];
24 bool can[N];
25
26 int find( int x) {
27 if (fa[x] == x) return fa[x];
28 else return fa[x] = find(fa[x]);
29 }
30 void Union( int x, int y) {
31 int xx = find(x), yy = find(y);
32 if (xx == yy) return ;
33 --cnt; fa[xx] = yy;
34 }
35 void addedge( int u, int v) {
36 eg[idx].u = u, eg[idx].v = v, eg[idx].nex = g[u];
37 g[u] = idx++;
38 }
39 void input() {
40 for ( int i = 0; i < n; ++i)
41 fa[i] = i;
42 cnt = n; idx = 0;
43 memset(can, 0, sizeof(can));
44 memset(g, - 1, sizeof(g));
45 int u, v;
46 for ( int i = 0; i < m; ++i) {
47 scanf( " %d%d ", &u, &v);
48 addedge(u, v);
49 }
50 scanf( " %d ", &c);
51 for ( int i = 0 ; i < c; ++i) {
52 scanf( " %d ", &ar[i]);
53 can[ar[i]] = true;
54 }
55 }
56 void solve() {
57 for ( int i = 0; i < n; ++i) {
58 if (can[i]) continue;
59 for ( int j = g[i]; ~j; j = eg[j].nex) {
60 if (can[eg[j].v]) addedge(eg[j].v, eg[j].u);
61 else Union(eg[j].u, eg[j].v);
62 }
63 }
64 int cc = 0;
65 ans[cc++] = cnt - c;
66 for ( int i = c - 1; i >= 0; --i) {
67 can[ar[i]] = false;
68 for ( int j = g[ar[i]]; ~j; j = eg[j].nex) {
69 if (can[eg[j].v]) addedge(eg[j].v, eg[j].u);
70 else Union(eg[j].u, eg[j].v);
71 }
72 ans[cc++] = cnt - i;
73 }
74 for ( int i = cc - 1; i >= 0; --i)
75 printf( " %d\n ", ans[i]);
76 }
77 int main() {
78 while ( 2 == scanf( " %d%d ", &n, &m)) {
79 input();
80 solve();
81 }
82 return 0;
83 }
六、数学
1、计数问题
1005:
参见( http://hi.baidu.com/aekdycoin/item/589a99d3fad167352b35c7d4 )
/*
我们解释下红字部分, 原理叫做普吕弗(Prüfer)序列 ,一棵树要得到普吕弗序列,方法是逐次去掉树的顶点,直到剩下两个顶点。考虑树T,其顶点为{1, 2, ..., n}。在第i步,去掉标号最小的叶,并把普吕弗序列的第i项设为这叶的邻顶点的标号。
一棵树的序列明显是唯一的,而且长为n − 2。
从一个普吕弗序列,可以求得一棵树有这一普吕弗序列。
已知序列还原树的算法:
*/
,其中每个点出现的次数=树中度数-1并且可以根据序列唯一的构造出一颗树
于是问题就转化为:对于给定的n-2个位置来安排点.使得满足所有条件
先考虑那些有限制的点,假设有 Tot 个(Tot <= n,很显然)
那么显然就一组合数学中的有重复元素的排列问题:
从n-2 个位置中选出来Tot个,然后进行有重复元素的全排列
其他的位置显然每一个位置都有(n-w)种可能,于是答案出来了
/********************************/
2 Problem: 1005
3 User: pikapika
4 Language: Java
5 Result: Accepted
6 Time:1328 ms
7 Memory:19132 kb
8 *************************************************************** */
9
10 import java.io.BufferedInputStream;
11 import java.math.BigInteger;
12 import java.util.Scanner;
13
14 public class Main {
15 public static BigInteger Cal( int n) {
16 BigInteger re = BigInteger.ONE;
17 for ( int i = 2; i <= n; ++i)
18 re = re.multiply(BigInteger.valueOf(i));
19 return re;
20 }
21 public static void main(String[] args) {
22 Scanner cin = new Scanner( new BufferedInputStream(System.in));
23 int ar[] = new int[1001];
24 int c = 0, k, sum = 0;
25 int n = cin.nextInt();
26 for ( int i = 0; i < n; ++i) {
27 k = cin.nextInt();
28 if (-1 == k) continue;
29 else {
30 sum += k - 1;
31 ar[c++] = k - 1;
32 }
33 }
34 BigInteger ans = Cal(sum);
35 for ( int i = 0; i < c; ++i)
36 ans = ans.divide(Cal(ar[i]));
37 ans = ans.multiply(BigInteger.valueOf(n - c).pow(n - 2 - sum));
38 ans = ans.multiply(Cal(n - 2)).divide(Cal(sum)).divide(Cal(n - 2 - sum));
39 System.out.println(ans);
40 }
41
42 }
1008:
题目可以看成n个格子,每个格子可以染1到m任意一个颜色,问有多少种情况存在任意两个相邻格子同色。
首先我们知道总共的染色方案是m^n次,那么我们只要反过来求有多少种不越狱的情况就可以了。
第一个格子有m种染色方案,后面的每个格子都不可以和前面一个格子同色,即m-1种方案,所以答案就是m^n - m * (m - 1) ^ (n - 1)
用快速幂可以迅速求出答案
2 Problem: 1008
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:0 ms
7 Memory:1272 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 using namespace std;
15 #define typ unsigned long long
16 const typ MOD = 100003;
17 typ n, m;
18
19 typ Pow(typ x, typ y) {
20 typ re = 1;
21 while (y > 0) {
22 if (y % 2 == 1)
23 re = (re * x) % MOD;
24 x = (x * x) % MOD;
25 y = y / 2;
26 }
27 return re;
28 }
29 int main() {
30 while (cin >> m >> n) {
31 typ ans = (Pow(m, n) - (m * Pow(m - 1, n - 1)) % MOD + MOD) % MOD;
32 cout << ans << endl;
33 }
34 return 0;
35 }
36
2、数学分析
1045:
[HAOI2008] 糖果传递这是一道陈题,原题是uva 11300
具体分析参见( http://www.cnblogs.com/kuangbin/archive/2012/10/24/2736733.html )
刘汝佳的《算法竞赛——入门经典》中第一章1.1 例题3(p4)中有具体分析
2 Problem: 1045
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:2712 ms
7 Memory:16432 kb
8 *************************************************************** */
9
10 #include
11 #include
12
13 typedef long long ll;
14 const int N = 1000000 + 10;
15 ll a[N], c[N];
16 int n;
17
18 void work() {
19 ll tot = 0;
20 for ( int i = 1; i <= n; ++i) {
21 scanf( " %lld ", &a[i]);
22 tot += a[i];
23 }
24 ll avg = tot / n;
25 c[ 1] = 0;
26 for ( int i = 2; i <= n; ++i) {
27 c[i] = c[i - 1] + avg - a[i - 1];
28 }
29
30 std::sort(c + 1, c + 1 + n);
31 ll mid = c[(n + 1) >> 1];
32 ll ans = 0;
33 for ( int i = 1; i <= n; ++i)
34 ans = ans + abs(c[i] - mid);
35
36 printf( " %lld\n ", ans);
37 }
38 int main() {
39 while ( 1 == scanf( " %d ", &n)) {
40 work();
41 }
42 return 0;
43 }
七、博弈
1022:
经典的ANTI-NIM博弈问题
在anti-Nim游戏中,先手必胜当且仅当:
(1)所有堆的石子数都为1且游戏的SG值为0;
(2)有些堆的石子数大于1且游戏的SG值不为0。
参见2009年国家集训队论文贾志豪论文《组合游戏略述 ——浅谈SG游戏的若干拓展及变形》
( http://wenku.baidu.com/view/25540742a8956bec0975e3a8.html )
2 Problem: 1022
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:40 ms
7 Memory:1272 kb
8 *************************************************************** */
9
10 #include
11 #include
12 #include
13 #include
14 #include
15 using namespace std;
16
17 int main() {
18 int T, v, g, n, k;
19 scanf( " %d ", &T);
20 while (T--) {
21 v = g = 0;
22 scanf( " %d ", &n);
23 for ( int i = 1; i <= n; ++i) {
24 scanf( " %d ", &k);
25 if (k == 1) ++ g;
26 v ^= k;
27 }
28 if (g == n) {
29 if (v == 0)
30 puts( " John ");
31 else puts( " Brother ");
32 } else {
33 if (v > 0)
34 puts( " John ");
35 else puts( " Brother ");
36 }
37 }
38 return 0;
39 }
////////////////////////
八、搜索
1024:
搜索
首先,要求分割出来的n块面积都一样,我们枚举当前矩形被切成两块时这两块矩形还将在以后被切几刀,于是相应的边长也被分成了对应比例。
2 Problem: 1024
3 User: pikapika
4 Language: C++
5 Result: Accepted
6 Time:184 ms
7 Memory:804 kb
8 *************************************************************** */
9
10 #include
11 #include
12
13 const double INF = 1e100;
14
15 double dfs( double x, double y, int c) {
16 if (y > x) std::swap(x, y);
17 if ( 1 == c) return x / y;
18 else {
19 double re = INF;
20 for ( int i = 1; i < c; ++i) {
21 re = std::min(re, std::max(dfs(x, y * i * ( 1. / c), i), dfs(x, y - y * i * ( 1. / c), c - i)));
22 }
23 for ( int i = 1; i < c; ++i) {
24 re = std::min(re, std::max(dfs(x * i * ( 1. / c), y, i), dfs(x - x * i * ( 1. / c), y, c - i)));
25 }
26 return re;
27 }
28 }
29 int main() {
30 double x, y;
31 int c;
32 while ( 3 == scanf( " %lf%lf%d ", &x, &y, &c)) {
33 printf( " %.6f\n ", dfs(x, y, c));
34 }
35 return 0;
36 }
转载于:https://www.cnblogs.com/hewifi/p/3307793.html