题目链接:各大OJ上都有。
闲话:毫不过分地说这题我做了一年。
前置知识:线段树(权值线段树和动态开点,也就是主席树)
30分做法:直接模拟即可。
code:
#include<iostream>
#include<cstdio>
using namespace std;const int N = 1010;
int n, m, q;
int que[N][N], a[300010];
void init() {int i, j;for (i = 1; i <= n; i++)for (j = 1; j <= m; j++)que[i][j] = (i - 1) * m + j;return ;
}void inits() {int i;for (i = 1; i <= m; i++)a[i] = i; return ;
}void lefts() {int i;for (i = 1; i <= m; i++) {if (i - 1 == 0) continue;else {if (a[i - 1] == 0) {a[i - 1] = a[i];a[i] = 0;}}}return ;
}void left(int x) {int i;for (i = 1; i <= m;i++) {if (i - 1 == 0) continue;else {if (que[x][i - 1] == 0) {que[x][i - 1] = que[x][i];que[x][i] = 0;} }}return ;
}void front() {int i;for (i = 1; i <= n; i++) {if (i - 1 == 0) continue;else {if (que[i - 1][m] == 0) {que[i - 1][m] = que[i][m];que[i][m] = 0;}}}return ;
}int main() {scanf("%d%d%d", &n, &m, &q);int i, x, y;if (n == 1) {inits();for (i = 1; i <= q; i++) {scanf("%d%d", &x, &y);printf("%d\n", a[y]);int temps = a[y];a[y] = 0;lefts();a[m] = temps;}return 0;}init();for (i = 1; i <= q; i++) {scanf("%d%d", &x, &y);printf("%d\n", que[x][y]);int temp = que[x][y];que[x][y] = 0;left(x);front();que[n][m] = temp;}return 0;
}
50分做法:可以发现我们其实只需要维护q行和最后一列就可以了,所以可以离散化一下,然后再模拟。
code:
#include<iostream>
#include<algorithm>
using namespace std;const int Q=510, Max=5e4+10;
typedef long long ll;
struct question{int x, y;
} que[Q];
int n, m, q, tmp[Q];
ll ans, map[Q][Max], lastline[Max];bool cmp(int a, int b) {return a<b;
}int main() {int tot;cin >> n >> m >> q;for (int i=1; i<=q; i++) {cin >> que[i].x >> que[i].y;tmp[i]=que[i].x;}for (int i=1; i<=n; i++)lastline[i]=i*m;sort(tmp+1, tmp+q+1, cmp);tot=unique(tmp+1, tmp+q+1)-tmp-1;for (int i=1; i<=tot; i++) {ll k=(ll)(tmp[i]-1)*(ll)m;for (int j=1; j<m; j++)map[i][j]=++k;}for (int i=1; i<=q; i++) {int newx;for (int j=1; j<=tot; j++)if (tmp[j]==que[i].x) {newx=j;break;}if (que[i].y==m) ans=lastline[tmp[newx]];else ans=map[newx][que[i].y];cout << ans << endl;if (que[i].y!=m) {for (int j=que[i].y; j<m-1; j++)map[newx][j]=map[newx][j+1];map[newx][m-1]=lastline[tmp[newx]];}for (int j=tmp[newx]; j<n; j++)lastline[j]=lastline[j+1];lastline[n]=ans;}return 0;
}
100分做法:同50分做法,我们用n颗线段树来维护前n行的m-1列和另一颗线段树来维护最后一列,这样对于每一个操作,我们只需从第x颗线段树中把排名为y的点删除,放入到最后一列的末端,然后再把最后一列第x行的点放入到第x行末端即可。注意,若是出队的点本来就位于最后一列,那么只需将其删除,放入最后一列末端即可。
由于要开n+1线段树,会发现要MLE,所以我们采用动态开点的方式(一般用多大内存就开多大)。
另外线段树每个节点需要维护的值是该节点所对应的的区间有多少点已经出队,这样子我们每次就可以很方便地查询排名为k的点。
查询时的操作就是主席树中查询区间第k大值的方法。
最后对于删除的点,为了方便再次查询时不至于出错,我们将其放入vector数组中,来进行维护。
具体可以看代码:
#include<iostream>
#include<vector>
#define mid (l+r)/2
using namespace std;typedef long long ll;
const int N=3e5+10, Max=1e7+10;
struct point {int l ,r, use;
} p[Max];
vector<ll> Tree[N];
int n, m, q;
int cnt, root[N];
int upper_limit;void build(int &rt, int x, int l, int r) {if (!rt) rt=++cnt;p[rt].use++;if (l<r) {if (x<=mid) build(p[rt].l, x, l, mid);else build(p[rt].r, x, mid+1, r);}return ;
}int rank(int rt, int k, int l, int r) {if (l==r) return l;int lsize=mid-l+1-p[p[rt].l].use;if (k<=lsize) return rank(p[rt].l, k, l, mid);else return rank(p[rt].r, k-lsize, mid+1, r);
}ll update_m(int x) {int r=rank(root[n+1], x, 1, upper_limit);build(root[n+1], r, 1, upper_limit);ll ans;if (r>n) ans=Tree[n+1][r-n-1];else ans=1LL*r*m;return ans;
}ll update_n(int x, int y) {int r=rank(root[x], y, 1, upper_limit);build(root[x], r, 1, upper_limit);ll ans;if (r>=m) ans=Tree[x][r-m];else ans=1LL*(x-1)*m+r;return ans;
}int main() {int x, y;cin >> n >> m >> q;upper_limit=max(n, m)+q;for (int i=1; i<=q; i++) {cin >> x >> y;if (y==m) {ll ans=update_m(x);Tree[n+1].push_back(ans);cout << ans << endl;}else {ll ans=update_n(x, y);Tree[n+1].push_back(ans);cout << ans << endl;ll tmp=update_m(x);Tree[x].push_back(tmp);}}return 0;
}
PS:对于主席树掌握比较熟练的同学,这道题目还是比较简单的。