Or Plus Max
需要求 iorj≤k,i=j 条件下 max{Ai+Aj},我们可以找出 k 的二进制子集内的最大值和次大值。
暴力枚举子集肯定会超时,所以我们可以利用已有的子集信息进行转移,具体就是尝试抠掉 k 的某一个 1,从这个子集转移一下最大值和次大值。
因为抠掉 1 之后的那个子集也是用同样的方法转移过来的,所以就相当于枚举子集了。
可以看代码实现,更好理解一些。
#include <algorithm> #include <cstdio> using namespace std;   const int N = 300005;   int n; int a[N]; int f[N], g[N], id[N]; 
  int main() {     scanf("%d", &n);     for(int i = 0; i < (1<<n); i++)         scanf("%d", &a[i]);       for(int i = 0; i < (1<<n); i++)     {         f[i] = a[i];         id[i] = i;         for(int j = 0; j < 18; j++)         {             if(!(i&(1<<j))) continue;             int k = i-(1<<j);              if(f[k] > f[i])             {                 g[i] = max({f[i], g[k]});                 f[i] = f[k];                 id[i] = id[k];             }             else             {                 if(f[k] > g[i] and id[k] != id[i])                     g[i] = f[k];                 else if(g[k] > g[i])                     g[i] = g[k];             }         }     }     int val = 0;     for(int i = 1; i < (1<<n); i++)     {         val = max(val, f[i]+g[i]);         printf("%d\n", val);     } }
   | 
 
LEGOndary Grandmaster
把两个相邻的相同的数取反这个操作不好做,可以考虑一个套路的做法:把偶数位置的数取反一下。
这样做的话这个操作就变成了交换两个相邻的数,因为交换相同的数没有用,所以求解最短距离时一定不会有这种操作,也就是不会出现非法操作。
现在用交换两个相邻的数的操作使得偶数位置取反后的两个串 s′,t′ 相同,其实就是求所有第 k 个 1 的位置差。
但是由于题目中有 ? 的存在,我们不太好求相同位置的 1 的位置差(因为不确定这是第几个 1)。
解决方法也很套路:拆贡献。具体就是统计每个间隔处需要交换多少次,很明显这个间隔处的前面 1 的数量差多少个就需要交换多少次。所以我们可以统计一下 1 的前缀和。
而题目需要求所有方案的距离和,所以我们可以设 gi,j 为在第 i 个间隔处, 1 的个数的差为 j(−i≤j≤i) 的方案数,这样记录一下的话转移就比较好写了。
因为下标有负数所以需要整体平移一下。
#include <cmath> #include <cstdio> #include <cstring> using namespace std;   typedef long long ll; const int N = 2005; const int P = 1e9+7;   int n; ll f[N][N*2]; ll g[N][N*2]; char s[N], t[N];   void solve();   int main() {     int t; scanf("%d", &t);     while(t --> 0) solve(); }   void solve() {     scanf("%d", &n);     scanf("%s%s", s+1, t+1);     for(int i = 1; i <= n; i++)     {         memset(f[i], 0, sizeof(f[i]));         memset(g[i], 0, sizeof(g[i]));     }     for(int i = 2; i <= n; i += 2)     {         if(s[i] != '?') s[i] ^= 1;         if(t[i] != '?') t[i] ^= 1;     }     g[0][N] = 1;     for(int i = 0; i < n; i++)     {         for(int j = -i; j <= i; j++)         {             for(int k = 0; k < 2; k++)                 for(int l = 0; l < 2; l++)                 {                     if(s[i+1] != '?' and k != s[i+1]-'0') continue;                     if(t[i+1] != '?' and l != t[i+1]-'0') continue;                     int p = j+k-l;                     (g[i+1][p+N] += g[i][j+N]) %= P;                     (f[i+1][p+N] += g[i][j+N]*abs(j) + f[i][j+N]) %= P;                 }         }     }     printf("%lld\n", f[n][N]); }
 
   | 
 
数列
先考虑求出原序列的 f(a) 值,很明显就是 max{ai−premini},于是考虑前缀 min 值。
可以发现,前缀 min 值相同的一段可以一起处理,前缀 min 值不同的段互不影响。
对于相同的一段,如果这一段中有 f(a) 值的话就需要对这段进行修改,我们可以选择一个位置,将这个位置前面的最小值都删掉,这个位置后面的最大值都删掉,这样就可以使 f(a) 值减小了,我们直接处理出前缀后缀,然后枚举这个位置就行了。
#include <algorithm> #include <climits> #include <cstdio> #include <cstring> using namespace std;   const int N = 1000005;   int n, d; int a[N]; int pre[N], suf[N];   int solve(int l, int r);   int main() {     int minv = 0x3f3f3f3f;     scanf("%d", &n);     for(int i = 1; i <= n; i++)     {         scanf("%d", &a[i]);         d = max(d, a[i] - minv);         minv = min(minv, a[i]);     }       int l = 1, r = 1, ans = 0;     minv = a[1];     for(int i = 2; i <= n+1; i++)     {         if(a[i] < minv or i == n+1)         {             ans += solve(l, r);             l = r = i;         }         else             r = i;         minv = min(minv, a[i]);     }       printf("%d", ans); }   int solve(int l, int r) {     int minv = INT_MAX;     int maxv = -INT_MAX;     for(int i = l; i <= r; i++)     {         minv = min(minv, a[i]);         maxv = max(maxv, a[i]);     }     if(maxv - minv != d)         return 0;     pre[l-1] = suf[r+1] = 0;     for(int i = l; i <= r; i++)         pre[i] = pre[i-1]+(a[i]==minv);     for(int i = r; i >= l; i--)         suf[i] = suf[i+1]+(a[i]==maxv);       int ans = INT_MAX;     for(int i = l-1; i <= r; i++)         ans = min({ans, pre[i]+suf[i+1]});     return ans; }
   |