J. Joy of Handcraft(并查集暴力染色)
2020 China Collegiate Programming Contest - Mianyang Site
J. Joy of Handcraft
time limit per test2 seconds
memory limit per test256 megabytes
inputstandard input
outputstandard output
Little Horse always does some handcrafts, which is full of joy. This time, he builds a circuit that can turn on and off the bulbs periodically.
There are n bulbs in the circuit, the i-th of which has a period ti and a luminance xi. Formally, the i-th bulb will be turned on from the (2kti+1)-th second to the (2kti+ti)-th second, and it will be turned off from the (2kti+ti+1)-th second to the (2kti+2ti)-th second, k=0,1,2,… When the i-th bulb is on, its luminance will be xi, otherwise its luminance will be 0.
Now, Little Horse wants to know, for each second from the first second to the m-th second, what's the maximum luminance among all the bulbs.
Input
The first line of the input contains an integer T (1≤T≤100) — the number of test cases.
The first line of each test case contains two integers n,m (1≤n,m≤105) — the number of bulbs, and the number of integers you need to output. The sum of n and the sum of m will not exceed 2×105.
Then in the next n lines, the i-th line contains two integers ti,xi (1≤ti,xi≤105) — the period and the luminance of the i-th bulb.
Output
The x-th test case begins with Case #x:, and there follow m integers. The i-th integer indicates the maximum luminance among all the bulbs in the i-th second. If no bulb is on in the i-th second, output 0.
有一堆灯,每个灯有一个亮度xi,和一个开启周期ti,每盏灯会在第(2kti+1)秒到第(2kti+ti)秒被点亮。k=0,1,2,…
输出m行,第 i 行一个数,表示第 i 秒亮着的所有灯中,最大的 xi 是多少。
赛后出题人给出两种思路,第一种二分线段树,第二种暴力染色。
我们用第二种。
维护两个数组,一个tr数组表示已被染过的连续区间的最右端点。一个ans数组表示当前点被染色的时候的xi。
先按xi从大到小排序。
并查集维护连续的被染区间的右端点。
然后依次对ans数组染色。
没染过的,tr保存为当次操作终点,并给ans赋值。
染过的,跳到连续区间被染过的终点,然后跳指针 k 和 j 。
先染的不需要被操作。
#include<stdio.h> #include<algorithm> #include<string.h> using namespace std; const int maxn = 1e5+5; int buc[maxn], tr[maxn], ans[maxn]; struct node { int t, x; }e[maxn]; int cmp(node aa, node bb) { return aa.x > bb.x; } int dfs(int k)// tr[k] 保存右端点 { if(k == tr[k]) return tr[k] = dfs(k+1); if(tr[k]) return tr[k] = dfs(tr[k]); return k-1; } int main() { int t, cnt = 0; scanf("%d", &t); while(t--) { int n, m; scanf("%d%d", &n, &m); int maxt = 0; for(int i = 1, tin, xin; i <= n; i++) { scanf("%d%d", &tin, &xin); buc[tin] = max(buc[tin], xin); maxt = max(maxt, tin); } int cntq = 0; for(int i = 1; i <= maxt; i++) if(buc[i]) e[++cntq].t = i, e[cntq].x = buc[i], buc[i] = 0; sort(e+1, e+cntq+1, cmp); for(int i = 1; i <= cntq; i++) { int tn = e[i].t, xn = e[i].x; for(int j = 1; j <= m; j += (tn<<1)) { int ed = (j+tn-1)>m?m:(j+tn-1); for(int k = j; k <= ed; k++) if(!tr[k]) tr[k] = ed, ans[k] = xn; else { k = dfs(k);//dfs 返回末端 tr[k] = max(ed, tr[k]); if(k > ed) //tr[k] = ed, ans[k] = xn; { while(j + (tn<<2) <= k) j += (tn<<1); } }//tr[i] 记录i所属的连续被染区间的右端点。 } }printf("Case #%d:",++cnt); for(int i = 1; i <= m; i++) printf(" %d", ans[i]),ans[i] = tr[i] = 0; puts(""); } return 0; }
赛场上想起一道寒假做过的类似的题。
https://codeforces.com/contest/999/problem/D
#include<stdio.h> #include<string.h> typedef long long ll; int a[200005]; int b[200005]; int tr[200005][2];//记录父节点 父节点记录下一个非满节点 满节点加入父结点中 int Find(int k) { if(tr[k][0] == k||tr[k][0] == -1) return k; else return tr[k][0] = Find(tr[k][0]); } int main() { ll n, m; scanf("%lld%lld",&n,&m); memset(tr,-1,sizeof(tr)); tr[0][1] = 0; ll k = n / m; ll jsq = 0; for(int i = 1; i <= n; i++) { scanf("%d",&a[i]); int t = Find(a[i] %m); if(b[t] == k) { int y = (tr[t][1] - a[i]%m + m) % m; jsq += y; a[i] += y; t = tr[t][1]; } b[t]++; if(b[t] == k)//更新区间 { tr[t][0] = t; tr[t][1] = (t + 1)%m; int x = t; if( tr[(t+1)%m][0] != -1) { x = Find((t+1)%m); tr[t][0] = x; } if( tr[(t-1+m)%m][0] != -1) tr[Find((t-1+m)%m)][0] = x; } } printf("%lld\n",jsq); for(int i = 1; i <= n; i++) printf("%d%c",a[i], (i == n ? '\n' : ' ')); return 0; }
我是fw,事先有了思路赛场上40多分钟没有改完。