题解:
第一题:
贪心
状压:我们发现110,100这样一列有两个1的一定不会组合,就算他们和后面凑成了0,那么其中一个必定可以和后面的构成0,所以我们不考虑他们的转移;
而0和1组合和0和0组合是等价的,因为我们不考虑一列两个1,所以i+ j ==> i&j, 最后看是否转移为0;
#include<bits/stdc++.h> using namespace std; const int M = (1 << 4) + 10; bool dp[2][M]; int ww[M][M], w[M][M], zw[M][M], a[5]; inline int get(int m){ if(m == 1)return a[1]; if(m == 2)return (a[1]<<1) + a[2]; if(m == 3)return (a[1]<<2) + (a[2]<<1) + a[3]; return (a[1]<<3) + (a[2]<<2) + (a[3]<<1) + a[4]; } void init(){ memset(ww, -1, sizeof(ww)); memset(w, -1, sizeof(w)); memset(zw, -1, sizeof(zw)); for(int i = 0; i < (1 << 2); i++){ for(int j = 0; j < (1 << 2); j++){ bool fg = 0; for(int k = 0; k < 2; k++){ if(((1<<k) & i) && ((1 << k) & j))fg = 1; }if(!fg)ww[j][i] = i & j; } } for(int i = 0; i < (1 << 3); i++){ for(int j = 0; j < (1 << 3); j++){ bool fg = 0; for(int k = 0; k < 3; k++){ if(((1<<k) & i) && ((1 << k) & j))fg = 1; }if(!fg)zw[j][i] = i & j; } } for(int i = 0; i < (1 << 4); i++){ for(int j = 0; j < (1 << 4); j++){ bool fg = 0; for(int k = 0; k < 4; k++){ if(((1<<k) & i) && ((1 << k) & j))fg = 1; }if(!fg)w[j][i] = i & j; } } } int main(){ freopen("prob.in","r",stdin); freopen("prob.out","w",stdout); int t, tt = 0; scanf("%d", &t); init(); while(t--){ int n, m, now = 0; bool fg = 0; scanf("%d%d", &n, &m); memset(dp, 0, sizeof(dp)); for(int i = 1; i <= n; i++){ for(int j = 1; j <= m; j++)scanf("%d", &a[j]); if(fg)continue; int s = get(m); now ^= 1; dp[now][s] = 1; if(m == 1){ if(!s)fg = 1; } else if(m == 4) for(int sc = 0; sc < (1 << 4); sc++){ if(dp[now^1][sc]){ dp[now][sc] = 1; if(w[s][sc] != -1)dp[now][w[s][sc]] = 1; } } else if(m == 2){ for(int sc = 0; sc < (1 << 2); sc++){ if(dp[now^1][sc]){ dp[now][sc] = 1; if(ww[s][sc] != -1)dp[now][ww[s][sc]] = 1; } } } else { for(int sc = 0; sc < (1 << 3); sc++){ if(dp[now^1][sc]){ dp[now][sc] = 1; if(zw[s][sc] != -1)dp[now][zw[s][sc]] = 1; } } } if(dp[now][0])fg = 1; //if(fg)cout<<i<<"KKKKKKKK"; } //printf("%d %d %d ",++tt, n, m); fg ? puts("YES") : puts("NO"); } }
第二题:贪心
#include<bits/stdc++.h> using namespace std; const int M = 1e5 + 10; #define ll long long int n; struct pp{ll s,a,b; int id;}st1[M], st2[M], p[M]; ll sum[2], tot[2], yp[2]; bool cmp(pp T, pp C){ return T.a - T.b > C.a - C.b; } bool cmp2(pp T, pp C){ return T.b - T. a > C.b - C.a; } int main() { freopen("pizza.in","r",stdin); freopen("pizza.out","w",stdout); ll totm = 0, SS; scanf("%d%I64d", &n, &SS); for(int i = 1; i <= n; i++){ scanf("%I64d%I64d%I64d", &p[i].s, &p[i].a, &p[i].b); p[i].id = i; if(p[i].a >= p[i].b)st1[++tot[1]] = p[i], sum[1] += p[i].s * p[i].a, yp[1] += p[i].s; else st2[++tot[2]] = p[i], sum[2] += p[i].s * p[i].b, yp[2] += p[i].s; totm += p[i].s; } sort(st1 + 1, st1 + 1 + tot[1], cmp); sort(st2 + 1, st2 + 1 + tot[2], cmp2); ll cas1, cas2; cas1 = cas2 = sum[2] + sum[1]; ll ned = ceil(1.0*totm / SS); ll h1 = ceil(1.0*yp[1] / SS); ll h2 = yp[2] - (ned - h1) * SS; int pw = tot[2]; while(h2 > 0){ if(h2 < st2[pw].s) { cas1 -= (st2[pw].b - st2[pw].a) * h2; h2 = 0; } else { cas1 -= (st2[pw].b - st2[pw].a) * st2[pw].s; h2 -= st2[pw].s; } pw--; } h2 = yp[1] - (h1 - 1) * SS; pw = tot[1]; while(h2 > 0){ if(h2 < st1[pw].s) { cas2 -= (st1[pw].a - st1[pw].b) * h2; h2 = 0; } else { cas2 -= (st1[pw].a - st1[pw].b) * st1[pw].s; h2 -= st1[pw].s; } pw--; } printf("%I64d ", max(cas1, cas2)); }
第三题:线段树优化dp
对于c(k,i)的修改,我们只需要记录一个lst的数组,那么他就制度lst + 1 到i有贡献;
#include<bits/stdc++.h> using namespace std; const int M = 20000 + 5, inf = 1e9; int n, m, pre[M], a[M], pos[M], dp[2][M]; struct Node{ Node *ls, *rs; int v, tag; void up(){ v = max(ls->v , rs->v); } void down(){ if(tag){ ls->tag += tag; rs->tag += tag; ls->v += tag; rs->v += tag; tag = 0; } } }pool[M << 2], *root, *tail = pool; #define Ls nd->ls, lf, mid #define Rs nd->rs, mid + 1, rg int query(int L, int R, Node *nd = root, int lf = 1, int rg = n){ if(L <= lf && rg <= R)return nd->v; nd->down(); int mid = (lf + rg) >> 1; int v = -inf; if(L <= mid)v = query(L, R, Ls); if(R > mid)v = max(v, query(L, R, Rs)); return v; } void modify(int L, int R, int val, Node *nd = root, int lf = 1, int rg = n){ if(L <= lf && rg <= R){ nd->v += val; nd->tag += val; return ; } nd->down(); int mid = (lf + rg) >> 1; if(L <= mid)modify(L, R, val, Ls); if(R > mid)modify(L, R, val, Rs); nd->up(); } Node *build(int vin, int lf = 1, int rg = n){ Node *nd = ++tail; if(lf == rg)nd->v = dp[vin][lf], nd->tag = 0; else { int mid = (lf + rg) >> 1; nd->ls = build(vin, lf, mid); nd->rs = build(vin, mid + 1, rg); nd->tag = 0; nd->up(); } return nd; } int main(){ freopen("scream.in","r",stdin); freopen("scream.out","w",stdout); scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++)scanf("%d", &a[i]); for(int i = 1; i <= n; i++){ pre[i] = pos[a[i]]; pos[a[i]] = i; } int now = 0, lst = 1; for(int i = 1; i <= n; i++)dp[lst][i] = dp[lst][i - 1] + (pre[i] == 0); for(int k = 2; k <= m; k++){ tail = pool; root = build(lst); for(int i = 1; i <= n; i++){ if(i < k)dp[now][i] = -inf; else { modify(max(pre[i], 1), i - 1, 1); dp[now][i] = query(1, i - 1); } } swap(now, lst); } printf("%d ", dp[lst][n]); }
今天前两道题都是贪心,我却一直在想dp, 贪心还是太烂了, 必须要加强重视了;