18.8.23 考试总结
今天的考试我是真的...emmmm
考试的时候我乱搞 然后狗了三十分...
一些大佬用状压dp弄得 然后还有另外一些大佬是贪心枚举所有情况 正解是非常简短的
这道题可以总结出一个规律 当m ≤ 5的时候 可以推出如果两行的数&起来是 0 那么就一定合法
证明: 因为m ≤ 5 又因为做过的题目小于等于没做过的 所以说0的个数一定大于等于1的个数
那么0最多的一行肯定至少有ceil(m / 2)个0 否则0就不够多
那么该行就有至多两个1 如果剩下的方案中没有一种方案在那两个1对应的两列全为0
那么该列0的个数就永远不可能大于等于1 就不合法
因为如果只有一列为0 另一列是1 那么那两列1的个数永远大于0的个数 就不可能合法
所以只需要找到两个&起来为0的就可以了 只有一个1的同理
代码
#include <iostream> #include <stdio.h> #include <cstring> using namespace std; const int N = 1000; int T,n,vis[N],x,m; inline int read( ) { int ans = 0,t = 1; char x; x = getchar( ); while(x < '0' || x > '9') { if(x == '-') t = -1; x = getchar( ); } while(x >= '0' && x <= '9') { ans = ans * 10 + x - '0'; x = getchar( ); } return ans * t; } int main( ) { freopen("prob.in","r",stdin); freopen("prob.out","w",stdout); T = read( ); while(T --) { memset(vis,0,sizeof(vis)); n = read( ); m = read( ); for(int i = 1;i <= n;i ++) { int v = 0; for(int j = 1;j <= m;j ++) { x = read( ); v <<= 1; v |= x; } vis[v] = true; } for(int i = 0;i < (1 << m);i ++) for(int j = 0;j < (1 << m);j ++) if(!(i & j) && vis[i] && vis[j]) { printf("YES\n"); goto Next; } printf("NO\n"); Next:; } }
这道题就是一道贪心...然后细节有点多
我们考虑把所有客人分为两组 a ≥ b的分为第一组 a < b 的分为第二组
然后再对两组分别排序 如果是第一组 则按照a - b的大小从大到小排序
另外一组则是b - a的大小从大到小排序
对于第一组而言 a - b的大小越大 那么也就是说如果让这个客人去吃第二组的饼饼 他亏得高兴度就会更多
所以我们想要让亏掉的高兴度最少 那么就优先满足a - b大的 第二组同理
那么现在问题就是如何分配饼饼 因为总饼数是一定的
所以我可能是第一组全部吃到喜欢的饼饼第二组的饼饼不够吃 然后第二组的人最后那几个就去吃第一组的饼饼
要不就是第一组的不够吃 最后几个人去吃第二组的 所以就乱搞搞判断就可以了
代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 1e5 + 5; ll sum = 0,ans,sum1,sum2,S; int n,m,num1 = 0,num2 = 0; struct gues { ll s; int a,b; }g1[N],g2[N]; inline int read( ) { int ans = 0,t = 1; char x; x = getchar( ); while(x < '0' || x > '9') { if(x == '-') t = -1; x = getchar( ); } while(x >= '0' && x <= '9') { ans = ans * 10 + x - '0'; x = getchar( ); } return ans * t; } bool cmp1(const gues & q,const gues & p) { return q.a - q.b > p.a - p.b; } bool cmp2(const gues & q,const gues & p) { return q.b - q.a > p.b - p.a; } void solve(ll s1,ll s2) { ll aa = s1 * S,bb = s2 * S; ll cmp = 0,rem = g1[1].s; int i = 1; for(i = 1;i <= num1 && aa > 0;i ++) { ll del = min(g1[i].s,aa); cmp += (1LL * g1[i].a * del); aa -= del; rem = g1[i].s - del; } if(i > 1) i --; if(s1 * S < sum1) { cmp += rem * 1LL * g1[i].b; i ++; bb -= rem; for(;i <= num1;i ++) { cmp += (1LL * g1[i].b * g1[i].s); bb -= g1[i].s; } } int j = 1; rem = g2[1].s; for(j = 1;j <= num2 && bb > 0;j ++) { ll del = min(g2[j].s,bb); cmp += (1LL * g2[j].b * del); bb -= del; rem = g2[j].s - del; } if(j > 1) j --; if(s2 * S < sum2) { cmp += rem * 1LL * g2[j].a; j ++; aa -= rem; for(;j <= num2;j ++) { cmp += (1LL * g2[j].a * g2[j].s); aa -= g2[j].s; } } ans = max(ans,cmp); } int main( ) { freopen("pizza.in","r",stdin); freopen("pizza.out","w",stdout); scanf("%d%I64d",& n,& S); for(int i = 1;i <= n;i ++) { int s,a,b; s = read( ); a = read( ); b = read( ); gues w; w.s = (ll)s; w.a = a,w.b = b; if(a >= b) { g1[++ num1] = w; sum1 += (ll)s; } else { g2[++ num2] = w; sum2 += (ll)s; } sum += (ll)s; } m = (sum % S == 0) ? 1LL * sum / S : 1LL * sum / S + 1; sort(g1 + 1,g1 + num1 + 1,cmp1); sort(g2 + 1,g2 + num2 + 1,cmp2); solve(sum1 / S,m - sum1 / S); if(m - sum1 / S - 1 >= 0) solve(sum1 / S + 1,m - sum1 / S - 1); if(sum1 / S - 1 >= 0) solve(sum1 / S - 1,m - sum1 / S + 1); printf("%I64d",ans); }
这道题可以说是真够鹅星了...呕
是一道线段树优化dp的问题 dp方程是显而易见的
dp[ i ][ j ]表示我选择i个冰淇淋 用了j个桶桶所能获得的最大收益
那么转移是和柠檬那道题 诗人小G很像的 dp[ i ][ j ] = dp[ k ][ j - 1 ] + c[ k + 1 ][ i ]
c[ k + 1 ][ i ]表示k + 1到 i 这一段的贡献 也就是有是多少种不同的冰淇淋
朴素的转移是枚举 i j k 然后暴力跑出来 k + 1 到 i 的贡献 但是这复杂度就是O(m * n2)
非常不优秀 然后我们可以发现dp只和上一层j - 1有关 就可以滚一滚
然后发现dp[ i ][ j ]是在1 ~ i - 1里面去一个max 所以考虑用线段树维护区间最大值
但是由于在dp的时候随着i的变化 c也是随着变化的 所以还要考虑怎么维护c
对于位置i 他比i - 1多了a[ i ]这一种颜色 但是a[ i ]只会在和他一样的冰淇淋上一次出现的位置 + 1 ~ i产生贡献
所以每次就只用在他上一次出现的位置 + 1 ~ i加上1就可以了 然后查询区间max
(这份代码线段树维护的是dp[ k ][ j ] + c[ k + 1 ][ i ]) 所以每次修改是从prev[i] 开始改到i - 1 我当时理解了好久...
代码
#include <bits/stdc++.h> #define oo 1e7 using namespace std; const int N = 2 * 1e5 + 5; int n,m,tag[4 * N],f[4 * N],dp[N][2],a[N]; int prev[N],las[N],now; void push_down(int o) { if(tag[o]) { f[2 * o] += tag[o]; f[2 * o + 1] += tag[o]; tag[2 * o] += tag[o]; tag[2 * o + 1] += tag[o]; tag[o] = 0; return ; } } void update(int o) { f[o] = max(f[2 * o],f[2 * o + 1]); } void modify(int o,int l,int r,int L,int R,int del) { if(l >= L && r <= R) { f[o] += del; tag[o] += del; return ; } push_down(o); int mid = (l + r) >> 1; if(L <= mid) modify(2 * o,l,mid,L,R,del); if(mid < R) modify(2 * o + 1,mid + 1,r,L,R,del); update(o); } int query(int o,int l,int r,int L,int R) { if(l >= L && r <= R) { return f[o]; } push_down(o); int mid = (l + r) >> 1; int ans = 0; if(L <= mid) ans = max(ans,query(2 * o,l,mid,L,R)); if(mid < R) ans = max(ans,query(2 * o + 1,mid + 1,r,L,R)); return ans; } void build(int o,int l,int r) { tag[o] = 0; if(l == r) { f[o] = dp[l][now ^ 1]; return ; } int mid = (l + r) >> 1; build(2 * o,l,mid); build(2 * o + 1,mid + 1,r); update(o); } 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]); prev[i] = las[a[i]]; las[a[i]] = i; } now = 0; for(int i = 1;i <= n;i ++) dp[i][now] = dp[i - 1][now] + (prev[i] == 0); for(int j = 2;j <= m;j ++) { now ^= 1; build(1,1,n); for(int i = 1;i <= n;i ++) { if(i < j) dp[i][now] = -oo; else { int pos = max(1,prev[i]); modify(1,1,n,pos,i - 1,1); dp[i][now] = query(1,1,n,1,i - 1); } } } printf("%d",dp[n][now]); }