【8.14校内测试】【DP专题】
nlogn做法,dp[i]表示当前长度为i的最长上升子序列末尾元素的值。
不会写lower_bound(qwq,贴一个以前的好看点的代码
#include<iostream>//使用lower_bound()函数 #include<algorithm> #include<cstdio> using namespace std; int a[30005],d[30005],n,len=0; int main() { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); d[++len]=a[1]; for (int i=2;i<=n;i++) if (a[i]>d[len])d[++len]=a[i]; else { int x=lower_bound(d+1,d+len+1,a[i])-d; d[x]=a[i]; } printf("%d\n",len); return 0; }
法一:用度数计算,可以证明最后答案等于选出的k个点的总度数-2*(k-2)-2,表示选择的一条链上去掉两头每个点少减两个度数,两头少减一个度数,就算有分叉也不影响。然后题目转换为求连续的k个点使他们的度数和最小。定义dp[u][k]表示以u为根的子树中选择k个点能获得的最小度数和(包括自己。背包转移。
大佬zyl的代码:
#include<stdio.h> #include<cstring> const int MAXN = 155; inline int min(int a, int b) { return a < b ? a : b; } struct Edge { int to; Edge *nxt; } pool[MAXN * 2], *head[MAXN], *tail = pool; inline void addEdge(int from, int to) { Edge *it = tail++; it -> to = to; it -> nxt = head[from], head[from] = it; } int n, k, ans; int dp[MAXN][MAXN], size[MAXN], w[MAXN]; void dfs(int u, int fa) { dp[u][0] = 0, dp[u][1] = w[u], size[u] = 1; for(Edge *it = head[u]; it; it = it -> nxt) { int v = it -> to; if(v == fa) continue; dfs(v, u); for(int i = size[u]; i >= 1; i--) for(int j = 0; j <= size[v] && i + j <= k; j++) if(i + j != 1) dp[u][i + j] = min(dp[u][i + j], dp[u][i] + dp[v][j]); size[u] += size[v]; } ans = min(ans, dp[u][k]); } int main() { freopen("isolate.in", "r", stdin); freopen("isolate.out", "w", stdout); scanf("%d%d", &n, &k); for(int i = 1; i < n; i++) { int u, v; scanf("%d%d", &u, &v); addEdge(u, v), addEdge(v, u); w[u]++, w[v]++; } memset(dp, 127 / 3, sizeof(dp)); ans = 0x7fffffff; dfs(1, 1); printf("%d", ans - k * 2 + 2); }
法二:定义dp[u][k]表示以u为根的子树中选择k个点最少需要割掉的边。同样是背包转移,如果儿子不选,dp[u][j]就要加一。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int n, k; const int N = 155; int stot, tov[N*2], nex[N*2], h[N]; void add ( int u, int v ) { tov[++stot] = v; nex[stot] = h[u]; h[u] = stot; } int siz[N], dp[N][N]; void dfs ( int u, int f ) { siz[u] = 1; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( v == f ) continue; dfs ( v, u ); siz[u] += siz[v]; } dp[u][1] = 0; for ( int i = h[u]; i; i = nex[i] ) { int v = tov[i]; if ( v == f ) continue; for ( int j = min ( k, siz[u] ); j; j -- ) { dp[u][j] = dp[u][j] + 1; for ( int p = 1; p <= min ( j, siz[v] ); p ++ ) { dp[u][j] = min ( dp[u][j], dp[v][p] + dp[u][j-p] ); } } } } int main ( ) { freopen ( "isolate.in", "r", stdin ); freopen ( "isolate.out", "w", stdout ); scanf ( "%d%d", &n, &k ); for ( int i = 1; i < n; i ++ ) { int u, v; scanf ( "%d%d", &u, &v ); add ( u, v ); add ( v, u ); } memset ( dp, 0x3f3f3f3f, sizeof ( dp ) ); dfs ( 1, 1 ); int ans = dp[1][k]; for ( int i = 2; i <= n; i ++ ) ans = min ( ans, dp[i][k] + 1 ); printf ( "%d", ans ); return 0; }
真的难受...以前写过当时秒掉的题,今天调了两个小时打死调不出来...【注意!!】像这种求方案数的状压dp通常是需要转移不放的情况的!!少了两句话直接就爆零了...
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int r, c; long long dp[2][1<<12]; int main ( ) { freopen ( "domino.in", "r", stdin ); freopen ( "domino.out", "w", stdout ); scanf ( "%d%d", &r, &c ); int now = 0; int l = max ( r, c ), p = min ( r, c ); r = p, c = l; int tot = ( 1 << r ) - 1; dp[now][tot] = 1; int pre = ( 1 << ( r - 1 ) ); for ( int i = 1; i <= c; i ++ ) for ( int j = 1; j <= r; j ++ ) { now ^= 1; memset ( dp[now], 0, sizeof ( dp[now] ) ); for ( int s = 0; s <= tot; s ++ ) { int up = s & pre; int las = s & 1; if ( !up ) { int ss = s << 1 | 1; dp[now][ss] += dp[now^1][s]; } if ( up && !las ) { if ( j != 1 ) { int ss = ( s ^ pre ) << 1 | 3; dp[now][ss] += dp[now^1][s]; } int ss = ( s ^ pre ) << 1; dp[now][ss] += dp[now^1][s]; } if ( up && las ) { int ss = ( s ^ pre ) << 1; dp[now][ss] += dp[now^1][s]; } } } printf ( "%I64d", dp[now][tot] ); return 0; }
实际上是一道比较简单的背包了...定义dp[k][p]表示子集s的和为k时s2的值为p的状态能否到达。如果dp[j][p]为true,dp[j+c[i]][p+c[i]]和dp[j+c[i]][p]都为true。【注意】当p>j时,dp[j+c[i]][p]依然需要转移!
#include<iostream> #include<cstdio> using namespace std; int n, k, c[505]; int dp[505][505]; int main ( ) { freopen ( "coin.in", "r", stdin ); freopen ( "coin.out", "w", stdout ); scanf ( "%d%d", &n, &k ); for ( int i = 1; i <= n; i ++ ) scanf ( "%d", &c[i] ); dp[0][0] = 1; for ( int i = 1; i <= n; i ++ ) { for ( int j = k-c[i]; j >= 0; j -- ) for ( int p = k; p >= 0; p -- ) { if(p<=k-c[i]) dp[j+c[i]][p+c[i]] |= dp[j][p]; dp[j+c[i]][p] |= dp[j][p]; } } int ans = 0; for ( int i = 0; i <= k; i ++ ) if ( dp[k][i] ) ans ++; printf ( "%d\n", ans ); for ( int i = 0; i <= k; i ++ ) if ( dp[k][i] ) printf ( "%d ", i ); return 0; }
可能是今天考了四道题的原因,做的过程中有点慌,一直死扣t2和t3导致最后没有时间看t4,t3这种类型下次一定不能再错了!!