这次应该叫高二上几调?
B. biology
错误思路:按a的值分个层,直接把每层的最大值的点编号放到队列里,后来忽然发现每个a最小一层的点都有可能当一次起点,它的位置决定了后面的点的位置,不能单独考虑起点b值的大小,于是就让每一个最小的a当一次起点循环一遍。我已经做好了它超时的心理准备,但是有WA的点就不太理解。。。如果有读者能指出错因我会很感谢的。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 4e6 + 3; const int N = 1e7 + 2; const ll INF = 1e17; int n, m, q[maxn], l=1, r, num, pos, hv[maxn], cnt; ll ans; struct node { int x, y, a, b; bool operator < (const node &T) { return a < T.a; } }p[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { n = read(); m = read(); //printf("n=%d m=%d\n", n, m); for(int i=1; i<=n; i++) { for(int j=1; j<=m ;j++) { p[++num].a = read(); //printf("p[%d].a = %d\n", num, p[num].a); p[num].x = i; p[num].y = j; } } num = 0; for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { p[++num].b = read(); } } sort(p+1, p+1+num); //printf("%d", p[1].a); for(int i=1; i<=num; i++) { if(p[i].a) { pos = i; break; } } q[++r] = pos; hv[++cnt] = pos; for(int i=pos+1; i<=num; i++) { //printf("p[%d].a = %d\n", i, p[i].a); //printf("cmp : %d %d\n", p[i].a, p[q[1]].a); if(p[i].a == p[q[1]].a) { hv[++cnt] = i; pos = i; } else break; } //printf("cnt=%d\n", cnt); for(int k=1; k<=cnt; k++) { ll sum = 0; l = 1, r = 0; q[++r] = hv[k]; for(int i=pos+1; i<=num; i++) { if(p[i].a > p[q[r]].a) q[++r] = i; else if(p[i].a == p[q[r]].a) { if(r == 1) { continue; } else if((ll)p[i].b+abs(p[i].x-p[q[r-1]].x)+abs(p[i].y-p[q[r-1]].y) > (ll)p[q[r]].b+abs(p[q[r]].x-p[q[r-1]].x)+abs(p[q[r]].y-p[q[r-1]].y)) { q[r] = i; } } } sum += p[q[1]].b; for(int i=2; i<=r; i++) { sum += (ll)p[q[i]].b+abs(p[q[i]].x-p[q[i-1]].x)+abs(p[q[i]].y-p[q[i-1]].y); } //printf("sum=%lld\n", sum); ans = max(ans, sum); } /*for(int i=pos+1; i<=num; i++) { if(p[i].a > p[q[r]].a) q[++r] = i; else if(p[i].a == p[q[r]].a) { if(r == 1) { if(p[i].b > p[q[r]].b) q[r] = i; } else if((ll)p[i].b+abs(p[i].x-p[q[r-1]].x)+abs(p[i].y-p[q[r-1]].y) > (ll)p[q[r]].b+abs(p[q[r]].x-p[q[r-1]].x)+abs(p[q[r]].y-p[q[r-1]].y)) { q[r] = i; } } }*/ /*ans += p[q[1]].b; for(int i=2; i<=r; i++) { ans += (ll)p[q[i]].b+abs(p[q[i]].x-p[q[i-1]].x)+abs(p[q[i]].y-p[q[i-1]].y); }*/ printf("%lld", ans); return 0; }
然后就是题解了:
第一步还是分层,但这个分层是放到数组里,而不是在脑海中。预处理的标准是每一个层号都能找到这一层的数值,而每一层的数值也能对应回层的编号。
dp[i][j] 表示当前路径的结尾在(i, j)位置的最大吸引度之和。 f[i][j] = max{f[i'][j'] + b[i'][j'] + ∣i − i ∣ + ∣j − j ∣, a[i][j] = a[i'][j'] + x, x>=1} 。
把绝对值拆开:f[i][j]可能为f[i'][j']+b[i'][j']-i'-j'+i+j或f[i'][j']+b[i'][j']+i'+j'-i-j或f[i'][j']-i'+j'+i-j或f[i'][j']+i'-j'+i+j。所以坐标转换,把(i+j, i-j)放进a[i][j]属于的分层,再用四个变量维护一下i'和j'的四种对应情况随递推的变化,递推时对应决定了当前的i和j应该加还是应该减。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 2e3 + 2; const int N = 4e6 + 3; const ll INF = 1e17; int a[maxn][maxn], b[maxn][maxn], n, m; int rec[N], pos[N], cnt; ll dp[maxn][maxn], ans, maxx[5]; bool vis[N]; vector<pair<int, int> > v[N]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } int main() { n = read(); m = read(); for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { a[i][j] = read(); if(!a[i][j]) continue; if(!vis[a[i][j]]) rec[++cnt] = a[i][j]; vis[a[i][j]] = 1; } } sort(rec+1, rec+1+cnt); for(int i=1; i<=cnt; i++) pos[rec[i]] = i; for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { b[i][j] = read(); } } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { if(!a[i][j]) continue; v[pos[a[i][j]]].push_back(make_pair(i+j, i-j)); } } for(int i=0; i<(int)v[1].size(); i++) { ll p = v[1][i].first, q = v[1][i].second; ll valb = b[(p+q)>>1][(p-q)>>1];//用原坐标的b值 maxx[1] = max(maxx[1], valb-p); maxx[2] = max(maxx[2], valb+p); maxx[3] = max(maxx[3], valb-q); maxx[4] = max(maxx[4], valb+q); } for(int i=2; i<=cnt; i++) { for(int j=0; j<(int)v[i].size(); j++) { ll p = v[i][j].first, q = v[i][j].second; ll valb = b[(p+q)>>1][(p-q)>>1]; //b'-i'-j'+b+i+j //b'+i'+j'+b-i-j //b'-i'+j'+b+i-j //b'+i'-j'+b-i+j dp[(p+q)>>1][(p-q)>>1] = max(max(maxx[1]+valb+((p+q)>>1)+((p-q)>>1), maxx[2]+valb-((p+q)>>1)-((p-q)>>1)), max(maxx[3]+valb+((p+q)>>1)-((p-q)>>1), maxx[4]+valb-((p+q)>>1)+((p-q)>>1))); } for(int j=0; j<(int)v[i].size(); j++) { ll p = v[i][j].first, q = v[i][j].second; ll valdp = dp[(p+q)>>1][(p-q)>>1]; maxx[1] = max(maxx[1], valdp-p);//b-i-j maxx[2] = max(maxx[2], valdp+p); maxx[3] = max(maxx[3], valdp-q); maxx[4] = max(maxx[4], valdp+q); } } for(int i=1; i<=n; i++) { for(int j=1; j<=m; j++) { ans = max(ans, dp[i][j]); } } printf("%lld\n", ans); return 0; }
C. english
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 3; const int N = 1e7 + 2; const ll INF = 1e17; const int mod = 1e9 + 7; int d[maxn][31], a[maxn], n, opt; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch > '9' || ch < '0') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } void RMQ_init() { for(int i=1; i<=n; i++) d[i][0] = a[i]; for(int j=1; (1<<j)<=n; j++) { for(int i=1; i+(1<<j)-1<=n; i++) { //printf("Main %d\n", i+(1<<(j-1))); //printf("cc %d %d\n", d[i][j-1], d[i+(1<<(j-1))][j-1]); d[i][j] = max(d[i][j-1], d[i+(1<<(j-1))][j-1]); //printf("d[%d][%d] = %d\n", i, j, d[i][j]); } } } int RMQ(int l, int r) { int k = 0; while(l+(1<<(k+1)) <= r) k++; //printf("k=%d\n", k); //printf("want:%d\n", r-(1<<k)+1); //printf("cmp : %d %d\n", d[l][k], d[r-(1<<k)+1][k]); return max(d[l][k], d[r-(1<<k)+1][k]); } void First() { ll ans = 0; for(int i=1; i<=n; i++) { for(int j=i+1; j<=n; j++) { ans = (ans+(ll)(a[i]^a[j])*RMQ(i, j))%mod; } } printf("%lld", ans); } void Second() { ll ans = 0; for(int i=1; i<=n; i++) { for(int j=i+1; j<=n; j++) { ll cat = RMQ(i, j); if((a[i]^a[j]) > cat) ans = ((ll)ans+cat)%mod; } } printf("%lld", ans); } int main() { n = read(); opt = read(); for(int i=1; i<=n; i++) { a[i] = read(); } RMQ_init(); /*int q = read(); for(int i=1; i<=q; i++) { int x = read(), y = read(); printf("%d\n", RMQ(x, y)); }*/ if(opt == 1) { First(); } else if(opt == 2) { Second(); } else if(opt == 3) { First(); printf("\n"); Second(); } return 0; }
超时的思路是先枚举每个区间的左右端点,再找每个区间的最大值(我还用了个RMQ),题目说什么我就干什么。
然而正解的第一步是找到每个数能作为最大值的最大区间,就是一个数在数组中左边比它大的第一个数和右边比它大的第一个数中间的区间,用单调栈来维护,不严格单调递减,左边比它大的第一个数就是栈中上一个数,右边比它大的第一个数就是让它出栈的那个数,这样就可以O(n)对于每个a[x],找到l[x]和r[x]。这些区间只有包含关系和不相交关系,没有相交关系,是被包含的区间作为孩子,就构成了一棵二叉树——假设a[i]是最大的数,它把整个数组分成了两部分,在左部分有一个最大的值覆盖的区间包含其余,右部分也是,接下来的就有种递归的感觉了,这说明它一定是二叉的。
后面的solution看不懂了。。。
void init() { int top = 0; for(int i=1; i<=n+1; i++) { while(top > 0 && a[i] > a[q[top]]) { r[q[top]] = i-1; l[q[top]] = q[top-1]+1; top--; } q[++top] = i; } } int main() { n = read(); opt = read(); for(int i=1; i<=n; i++) { a[i] = read(); } a[n+1] = INF; init(); for(int i=1; i<=n; i++) { printf("l[%d] = %d r[%d] = %d\n", i, l[i], i, r[i]); } return 0; }
除了新加一个极大地n+1之外,还有一种预处理的方法:
int main() { H = read(); opt = read(); int l = 1, r = 0; for(int i=1; i<=H; i++) { A[i] = read(); while(l<=r && A[i]>A[D[r]]) R[D[r--]] = i-1; L[i] = D[r]+1; D[++r] = i; for(int j=0; j<=22; j++) { one[i][j] = one[i-1][j]; zer[i][j] = zer[i-1][j]; if(A[i] & (1<<j)) one[i][j]++; else zer[i][j]++; } //if(opt >= 2) Insert(i); } while(l <= r) R[D[r--]] = H; for(int i=1; i<=H; i++) { printf("%d %d\n", L[i], R[i]); } return 0; }
(⊙o⊙)…Copy
/* 好吧,下文的注释都是我自己的胡思乱想,一问题解的作者就发现全都想错了 */ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 1e5 + 100; const int mod = 1e9 + 7; const int INF = 2147483647; int L[maxn], R[maxn], st[maxn], top, trie[maxn*22][2], tot=1; int rat[maxn*22], n_1[maxn][22], n_0[maxn][22], n, opt, A[maxn], root[maxn]; inline int read() { int x = 0, f = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') { f = -1; } ch = getchar(); } while(ch >= '0' && ch <= '9') { x = (x << 1) + (x << 3) + (ch^48); ch = getchar(); } return x * f; } //对每一个区间维护一棵01Trie树 = 来一个可持久化Trie inline void Insert(int a) { int pre = root[a-1]; root[a] = ++tot; int now = tot; for(int i=20; i>=0; i--) { if(pre) { trie[now][0] = trie[pre][0]; trie[now][1] = trie[pre][1]; } bool op = ((A[a]&(1<<i))>0); rat[now] = rat[pre] + 1;//rat[]'s use?一定会多1吗? //一定会多1,因为now是新建的节点,多加了一个数??大小关系怎么确定? //rat代表如果更大的话,即“子树的大小”,但这里的子树不是节点,而是now的左子树上有几个“边缘” //也就是从根到now这个节点代表的这一位都相同,在now的后一位更小了的数的数目 //不过这大概是一种错误的理解,这样的话ans显然加多了??? //只是一个点怎么会有大小之分??所以ret[now]存的是经过now这个点的数的个数 trie[now][op] = ++tot; //同时转向子树:为了省空间,只开必要的节点,而不是新建一条链 now = tot; if(pre) pre = trie[pre][op]; } rat[now] = rat[pre] + 1;//那这里为什么要重复? } inline int Query(int now, int a, int b)//A[a]^? > A[b]满足条件的?有几个 { int ans = 0; for(int i=20; i>=0; i--) { bool x = ((A[a]&(1<<i))>0), y = ((A[b]&(1<<i))>0); if(!y) { ans = ans+rat[trie[now][x^1]]; now = trie[now][x]; } else //在同一位上,y=1代表a已经没有了比b大的机会?但这只是拆开的单独一位啊 { now = trie[now][x^1]; } if(!now) break; } return ans; } int main() { n = read(); opt = read(); root[0] = 1; for(int i=1; i<=n; i++) { A[i] = read(); while(top > 0 && A[i]>=A[st[top]]) { R[st[top]] = i-1; top--; } L[i] = st[top] + 1; st[++top] = i; if(opt == 1 || opt == 3) { for(int j=0; j<=20; j++) { //分别记录第j位为1和第j位为0的前缀和 n_1[i][j] = n_1[i-1][j]; n_0[i][j] = n_0[i-1][j]; if((1<<j)&A[i]) n_1[i][j]++; else n_0[i][j]++; } } if(opt == 2 || opt == 3) { Insert(i); } } A[n+1] = INF; while(top > 0 && A[n+1]>=A[st[top]]) { R[st[top]] = n; top--; } ll ans1 = 0, ans2 = 0; for(int i=1; i<=n; i++) { if(i-L[i] < R[i]-i) { if(opt == 1 || opt == 3) { for(int j=L[i]; j<=i; j++)//不对啊,为什么两边可以同时取等? { for(int k=0; k<=20; k++) { if((1<<k)&A[j]) //因为没有影响,i已经是相反的了 { ans1 = (ans1+1ll*(n_0[R[i]][k]-n_0[i-1][k])*(1<<k)%mod*A[i]%mod)%mod; } else //它也取等一样解释?? { ans1 = (ans1+1ll*(n_1[R[i]][k]-n_1[i-1][k])*(1<<k)%mod*A[i]%mod)%mod; } } } } if(opt == 2 || opt == 3) { for(int j=L[i]; j<=i; j++) { //什么意思?这就是题解上说的启发式合并?? //右区间有多少个数满足 v^a[i] > a[x],x是最大值,i是循环的右区间 ans2 = (ans2+(ll)A[i]*(Query(root[R[i]], j, i)-Query(root[i-1], j, i)))%mod; } } } else { if(opt == 1 || opt == 3) { for(int j=i; j<=R[i]; j++) { for(int k=0; k<=20; k++) { if((1<<k)&A[j]) { ans1 = (ans1+1ll*(n_0[i][k]-n_0[L[i]-1][k])*(1<<k)%mod*A[i]%mod)%mod; } else { ans1 = (ans1+1ll*(n_1[i][k]-n_1[L[i]-1][k])*(1<<k)%mod*A[i]%mod)%mod; } } } } if(opt == 2 || opt == 3) { for(int j=i; j<=R[i]; j++) { ans2 = (ans2+(ll)A[i]*(Query(root[i], j, i)-Query(root[L[i]-1], j, i)))%mod; } } } } if(opt == 1 || opt == 3) printf("%lld\n", ans1); if(opt == 2 || opt == 3) printf("%lld\n", ans2); return 0; }