01
树形dp。。。比赛完看了看解题报告。发现我的状态写的太搓了,想到了先只考虑半数的,然后在一个全是A(或B)的块里面补上一个全的。当时没想着把这个也加到状态里去,写特判写的想哭。。。!
f[t][f][0] 表示t为根的子树,t被f攻击,并且算是f攻击的“连通块“里没有一个节点的攻击值是全值的最小值。
f[t][f][1] 表示t为根的子树,t被f攻击,并且算是f攻击的“连通块“里有一个节点的攻击值是全值的最小值。
sum = Σ{min(f[t->i][f][0], f[t->i][f^1][1])}; plus = min{ f[t->i][f][1] - min(f[t->i][f][0], f[t->i][f^1][1])} //把某个值补全 f[t][f][0] = cost[t][f]/2 + sum; f[t][f][1] = min(cost[t][f] + sum, cost[t][f]/2 + sum + plus);
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using namespace std; const int N = 220; int dp[N][2][2]; int c[N][2]; int mp[N][N], n; bool vis[N]; void dfs(int t, int f) { if(dp[t][f][0] != -1 && dp[t][f][1] != -1) return ; int sum = 0, p = inf, i; for(i = 1; i <= n; ++i) { if(!vis[i] && mp[t][i]) { vis[i] = true; dfs(i, f); dfs(i, f^1); sum += min(dp[i][f][0], dp[i][f^1][1]); p = min(dp[i][f][1] - min(dp[i][f][0], dp[i][f^1][1]), p); ///det = min{dp[v][j][1] - min(dp[v][j][0], dp[v][1-j][1])}; vis[i] = false; } } dp[t][f][0] = sum + c[t][f]/2; dp[t][f][1] = min(c[t][f] + sum, c[t][f]/2 + sum + p); } void init(int n) { for(int i = 0; i<= n; ++i) { for(int j = 0; j < 2; ++j) { for(int k = 0; k < 2; ++k) dp[i][j][k] = -1; } } CL(vis, false); } int main() { //freopen("data.in", "r", stdin); int i, x, y, ans; while(~scanf("%d", &n)) { init(n); for(i = 1; i <= n; ++i) scanf("%d", &c[i][0]); for(i = 1; i <= n; ++i) scanf("%d", &c[i][1]); CL(mp, 0); for(i = 1; i < n; ++i) { scanf("%d%d", &x, &y); mp[x][y] = mp[y][x] = 1; } init(n); vis[1] = true; dfs(1, 0); ans = dp[1][0][1]; //printf("%d ", ans); init(n); vis[1] = true; dfs(1, 1); //printf("%d\n", dp[1][1][1]); ans = min(ans, dp[1][1][1]); printf("%d\n", ans); } return 0; }
03 01背包问题的变形
按斜率排序,因为01背包的过程是由上一层的状态推当前层的状态。所以可以把斜率相同的点放到一个背包里,按离原点的距离进行压缩,把前面所有点的状态压到当前点,然后决策这个点选还是不选。因为这些点放到同一层里进行决策,所以不会出现重复选择某个点的情况。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using namespace std; const int N = 220; int f[40010]; struct node { int x; int y; int t; int val; bool operator < (const node tmp) const { if(y*tmp.x == x*tmp.y) return y < tmp.y; return y*tmp.x < x*tmp.y; } }p[N]; int main() { //freopen("data.in", "r", stdin); int n, T, i, j, k; int t, v, l, cas = 0; while(~scanf("%d%d", &n, &T)) { for(i = 0; i < n; ++i) { scanf("%d%d%d%d", &p[i].x, &p[i].y, &p[i].t, &p[i].val); } sort(p, p + n); //for(i = 0; i < n; ++i) printf("%d %d %d %d\n", p[i].x, p[i].y, p[i].t, p[i].val); CL(f, 0); for(i = 0; i < n; ++i) { k = i; while(p[i].y*p[k+1].x == p[k+1].y*p[i].x && k + 1 < n) k++; for(j = T; j >= p[i].t; --j) { t = 0, v = 0; for(l = i; l <= k; ++l) { t += p[l].t; v += p[l].val; //压缩 if(j - t >= 0) { if(f[j-t] != -inf) { f[j] = max(f[j], f[j-t] + v); } } else break; } } i = k; } int ans = 0; for(i = 0; i <= T; ++i) ans = max(ans, f[i]); printf("Case %d: %d\n", ++cas, ans); } return 0; }
04 完全是推规律
这个题M是我推出来的, 是gb推出来的。Orz
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int main() { //freopen("data.in", "r", stdin); int t, n, m, x; LL k, i; scanf("%d", &t); while(t--) { scanf("%d", &n); if(n == 1) { printf("2 2\n"); } else if(n == 2) { printf("3 3\n"); } else { m = 2; x = 2; i = 1; k = 1; while(1) { if(n - x <= 0) break; n -= x; m += 1 + x; k += i*LL(x) + i + 1; x += 2; i++; } m += n - 1; k += i*LL(n); cout << m << " " << k << endl; } } return 0; }
06
详见:Miller-Rabin + poLLard-rho 模板
07
首先明白一点。每种方案的循环次数 = 各循环节大小的最小共倍数。
然后就可以枚举循环节的大小,要求这些循环节的和<= N,为什么可以<,因为其余的可以全部是1;
因为是要找最小共倍数的个数,所以如果这些数包含大于一个质因子,那么最小共倍数就是一定的,多加几个只会增加和,不会得到新的最小共倍数。
所以可以推出:枚举的所有循环节大小都是只包含一个质因子。然后递推或者记忆化搜索就可以了。
f[i][n]表示最大为n,当前枚举的素数是素数表中第i个素数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <iostream> #include <cstdio> #include <cmath> #include <vector> #include <cstring> #include <algorithm> #include <string> #include <set> #include <ctime> #include <queue> #include <map> #include <sstream> #define CL(arr, val) memset(arr, (val), sizeof(arr)) #define REP(i, n) for((i) = 0; (i) < (n); ++(i)) #define FOR(i, l, h) for((i) = (l); (i) <= (h); ++(i)) #define FORD(i, h, l) for((i) = (h); (i) >= (l); --(i)) #define L(x) (x) << 1 #define R(x) (x) << 1 | 1 #define MID(l, r) ((l) + (r)) >> 1 #define Min(x, y) (x) < (y) ? (x) : (y) #define Max(x, y) (x) < (y) ? (y) : (x) #define E(x) (1 << (x)) #define iabs(x) ((x) > 0 ? (x) : -(x)) typedef long long LL; const double eps = 1e-8; //const int inf = ~0u>>2; using namespace std; const int N = 1024; LL f[N][N]; int prime[N], cnt, lim; bool isp[N]; void init() { CL(isp, true); CL(prime, 0); int i, j; for(i = 2; i < N; ++i) { for(j = i*i; j < N; j += i) isp[j] = false; } cnt = 0; for(i = 2; i < N; ++i) { if(isp[i]) {prime[cnt++] = i; } } } LL solve(int i, int n) { if(prime[i] > n) return 1; if(f[i][n] != -1) return f[i][n]; f[i][n] = solve(i + 1, n); LL x = prime[i]; while(x <= n) { f[i][n] += solve(i + 1, n - x); x *= prime[i]; } return f[i][n]; } int main() { freopen("data.in", "r", stdin); init(); int n; while(~scanf("%d", &n)) { CL(f, 0xff); cout << solve(0, n) << endl; } return 0; }
11
Lucas定理:
给出n, m, p。
把n, m写成p进制:
这里是二进制:
比如 n=1001101,m是从000000到1001101的枚举,我们知道在该定理中 C(0,1)=0, C(0, 0) = 0!/(0!*(0-0)!) = 1.
因此如果n=1001101的0对应位置的m二进制位为1那么C(n,m) % 2==0, 因此m对应n为0的位置只能填0,而1的位置填0,填1都是1(C(1,0)=C(1,1)=1);
不影响结果为奇数,并且保证不会出n的范围,因此所有的情况即是n中1位置对应m位置0,1的枚举,那么结果很明显就是:2^(n中1的个数)