The 13th Shandong ICPC Provincial Collegiate Programming Contest

写在前面

补题地址:https://codeforces.com/gym/104417

以下按个人向难度排序。

妈的调休太顶了受不了了找场省赛打打找找信心。

赛时 dztlb 大神喜成超级战犯写三题挂两题哈哈

I 签到

直接三重循环模拟。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================

//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int a, b; std::cin >> a >> b;

  int ans = 0;
  for (int i = 1; i <= 6; ++ i) {
    for (int j = 1; j <= 6; ++ j) {
      for (int k = 1; k <= 6; ++ k) {
        int cnt1 = 0, cnt2 = 0;
        if (i == 1 || i == 4) cnt1 += i;
        else cnt2 += i;
        if (j == 1 || j == 4) cnt1 += j;
        else cnt2 += j;
        if (k == 1 || k == 4) cnt1 += k;
        else cnt2 += k;

        if (cnt1 == a && cnt2 == b) ans = 1;
      }
    }
  }
  std::cout << (ans ? "Yes" : "No") << "\n";
  return 0;
}

A 签到

排序后直接模拟到达第 \(a_i\) 天时货物的剩余量即可。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 110;
//=============================================================
LL n, k;
struct Data {
  int a, b;
} a[kN];
//=============================================================
bool cmp(Data fir_, Data sec_) {
  return fir_.a < sec_.a;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n >> k;
    for (int i = 1; i <= n; ++ i) {
      std::cin >> a[i].a >> a[i].b;
    }
    std::sort(a + 1, a + n + 1, cmp);
    
    int flag = 1;
    LL sum = 0, last = 0;
    for (int i = 1; i <= n; ++ i) {
      sum += 1ll * k * (a[i].a - last);
      if (sum < a[i].b) flag = 0;
      sum -= a[i].b;
      last = a[i].a;
    }
    std::cout << (flag ? "Yes" : "No") << "\n";
  }
  return 0;
}

D 二分答案,贪心

显然考虑二分答案,则 \(v\) 不小于 \(\operatorname{mid}\) 的人都可以尝试背人,否则只能被人背。

然后考虑人 \(i\) 能否在保证合法情况下背起 \(j\),若合法则有:

\[v_{i} - (w_j - w_i)\ge \operatorname{mid} \iff v_i + w_i \ge \operatorname{mid} + w_j \]

发现通过上式可知, \(v_i + w_i\) 越大的人越有能力背起 \(w_j\) 较大的人。于是考虑贪心地匹配,将 \(v\ge \operatorname{mid}\) 的人按 \(v_i + w_i\) 降序排序,将 \(v<\operatorname{mid}\) 的人按 \(w_j\) 降序排序,按顺序尝试匹配即可。

二分答案后总时间复杂度 \(O(n\log v\log n)\)

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
//=============================================================
int n;
struct Data {
  int v, w;
} a[kN];
//=============================================================
bool cmp1(Data fir_, Data sec_) {
  return fir_.v < sec_.v;
}
bool cmp2(Data fir_, Data sec_) {
  return fir_.w > sec_.w;
}
bool cmp3(Data fir_, Data sec_) {
  return fir_.v + fir_.w > sec_.v + sec_. w;
}
bool check(int mid_) {
  std::vector<Data> yes, no;
  for (int i = 1; i <= n; ++ i) {
    if (a[i].v >= mid_) yes.push_back(a[i]);
    else no.push_back(a[i]);
  }
  std::sort(no.begin(), no.end(), cmp2);
  std::sort(yes.begin(), yes.end(), cmp3);
  int cnt = 0;
  for (int i = 0, j = 0, sz1 = no.size(), sz2 = yes.size(); 
       i < sz1 && j < sz2; ) {
    if (mid_ + no[i].w <= yes[j].v + yes[j].w) ++ cnt, ++ i, ++ j;
    else break;
  }
  return (cnt == (int) no.size());
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) std::cin >> a[i].v >> a[i].w;
    std::sort(a + 1, a + n + 1, cmp1);
    int ans = 1;
    for (int l = 1, r = 1e9; l <= r; ) {
      int mid = (l + r) >> 1;
      if (check(mid)) {
        ans = mid;
        l = mid + 1;
      } else {
        r = mid - 1;
      }
    }
    std::cout << ans << "\n";
  }
  return 0;
}
/*
2
5
10 5
1 102
10 100
7 4
9 50
2
1 100
10 1
*/

G 排序,贪心

wenqizhi 大爹秒了,看题解感觉也是和上题一样的水水题呃呃

code by wenqizhi:

#include<bits/stdc++.h>
using namespace std;

#define ll long long
#define ull unsigned long long

int read()
{
    int x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

const int N = 1e5 + 5;
int n, a[N];
struct node
{
    ll val, a;
    node(){ val = a = 0; }
}b[N];
int belong[N], num;
multiset<ll> S[N];
bool cmp(node a, node b)
{ return a.val < b.val ; }

void solve()
{
    n = read();
    for(int i = 1; i <= n; ++i) a[i] = read(), b[i].val = a[i] - i, b[i].a = a[i];
    sort(b + 1, b + n + 1, cmp);
    num = 1, belong[1] = 1;
    for(int i = 2; i <= n; ++i)
    {
        if(b[i].val == b[i - 1].val ) belong[i] = belong[i - 1];
        else belong[i] = ++num;
    }
    for(int i = 1; i <= num; ++i) S[i].clear();
    for(int i = 1; i <= n; ++i) S[belong[i]].insert(b[i].a );
    ll ans = 0;
    for(int i = 1; i <= num; ++i)
    {
        while(S[i].size() >= 2)
        {
            auto it = --S[i].end(), tmp = --(--S[i].end());
            ll a = *it, b = *tmp;
            S[i].erase(it), S[i].erase(tmp);
            if(a + b > 0)
            {
                ans += a + b;
            }else break;
        }
    }
    printf("%lld\n", ans);
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

L 构造,思维

这图画得太好了懒得写了。

image.png

code by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n,x,y;
int cnt;
int xx[N],yy[N],w[N],h[N];
int a,b,c,d;
signed main(){
	cin>>n>>x>>y;
	puts("Yes");
	a=x,b=y,c=x,d=y;
	for(int i=1;i<=n;++i){
		if(a>1&&b>1){
			--a,--b;
			++cnt;
			xx[cnt]=a;
			yy[cnt]=b;
			w[cnt]=c-a;
			h[cnt]=d-b;
		}
		if(c<n&&d<n){
			++c,++d;
			++cnt;
			xx[cnt]=c;
			yy[cnt]=d;
			w[cnt]=a-c;
			h[cnt]=b-d;
		}
		if(c<n&&b>1){
			++c,--b;
			++cnt;
			xx[cnt]=c;
			yy[cnt]=b;
			w[cnt]=-(c-a);
			h[cnt]=d-b;
		}
		if(d<n&&a>1){
			++d,--a;
			++cnt;
			xx[cnt]=a;
			yy[cnt]=d;
			w[cnt]=c-a;
			h[cnt]=-(d-b);
		}
	}
	cout<<cnt<<'\n';
	for(int i=1;i<=cnt;++i){
		cout<<xx[i]<<' '<<yy[i]<<' '<<w[i]<<' '<<h[i]<<'\n';
	}
	return 0;
}

B 模拟,拓扑排序

考虑对于每个工程看做一个点,\(m\) 个限制看做 \(m\) 个入度,\(k\) 个奖励看做 \(k\) 个出度,则问题类似从初始状态开始拓扑排序,求最终能经过多少个点。

发现当且仅当 \(m\) 个限制对应的工种人数,在获得奖励时,才有可能进一步地删去入度。一个显然的想法是考虑对于每个工种人数的限制,都记录它们的限制数量与对应的工程,然后升序排序;在完成某个工程获得奖励后,依次检查对应的限制是否已经满足,直至第一个不满足的限制,在此过程中删去已满足的限制对应的入度,即可判断是否可以选择对应的工程。

比较会用 STL 就会写得很舒服。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
#define pr std::pair
#define mp std::make_pair
const int kN = 1e5 + 10;
//=============================================================
int g, n, ans;
int into[kN];
std::map <LL, int> cnt;
std::map <int, std::vector<pr <LL, int> > > limit;
std::vector<pr <int, int> > out[kN];
//=============================================================
void topsort() {
  std::queue<int> q;
  for (auto &x: limit) {
    std::sort(x.second.begin(), x.second.end());
    while (!x.second.empty() && cnt[x.first] >= -x.second.back().first) {
      -- into[x.second.back().second];
      x.second.pop_back();
    }
  }
  for (int i = 1; i <= n; ++ i) if (!into[i]) q.push(i);

  while (!q.empty()) {
    int u = q.front(); q.pop();
    ++ ans;
    for (auto [x, y]: out[u]) {
      cnt[x] += y;
      while (!limit[x].empty() && cnt[x] >= -limit[x].back().first) {
        if (!(-- into[limit[x].back().second])) {
          q.push(limit[x].back().second);
        }
        limit[x].pop_back();
      }
    }
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> g;
  for (int i = 1; i <= g; ++ i) {
    int t, u; std::cin >> t >> u;
    cnt[t] = u;
  }
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) {
    int m; std::cin >> m;
    into[i] = m;
    for (int j = 1; j <= m; ++ j) {
      int a, b; std::cin >> a >> b;
      limit[a].push_back(mp(-b, i));
    }
    int k; std::cin >> k;
    for (int j = 1; j <= k; ++ j) {
      int c, d; std::cin >> c >> d;
      out[i].push_back(mp(c, d));
    }
  }
  topsort();
  std::cout << ans << "\n";
  return 0;
}

E 数学,结论,模拟

先特判 \(m | n\)\(k=1\) 的特殊情况。

发现操作 1 后的操作 2 会将操作 1 的影响完全还原导致白白浪费代价,则显然仅会先进行操作 2 后进行操作 1。

显然操作 2 至多进行 \(\log_k n\) 次,于是考虑直接枚举操作 2 的次数。设之后经过 \(i\) 次操作 1 后,记此时 \(n\) 的取值范围为 \([L_i, R_i]\),则显然有 \(L_i = k\cdot L_{i - 1}, R_i = k\cdot R_{i - 1}\),仅需检查该区间内是否有 \(m\) 的倍数,即可得最小操作 1 次数。

\(m\le 10^9\),则同样至多进行 \(O(\log_k m)\) 次操作 1 即可使区间 \([L_i, R_i]\) 长度扩大到不小于 \(m\),使得其中一定存在 \(m\) 的倍数,此时区间端点大小不大于 \(n\times m\le 10^{18}\times 10^9 =10^{27}\),需要开 int128。

时间复杂度 \(O(\log^2 k)\) 级别。

#include<bits/stdc++.h>
using namespace std;

#define ll __int128
#define ull unsigned long long

ll read()
{
    ll x = 0; bool f = false; char c = getchar();
    while(c < '0' || c > '9') f |= (c == '-'), c = getchar();
    while(c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c & 15), c = getchar();
    return f ? -x : x;
}

void write(ll x_) {
    if (!x_) return ;
    int fl = 0;
    if (x_ < 0) fl = 1, x_ = -x_; 

    char c = (x_ % 10 + '0');
    write(x_ / 10);
    putchar(c);
    if (fl) putchar('-');
}

void solve()
{
    ll n = read(), K = read(), m = read(), a = read(), b = read();
    ll ans = 0x7fffffffffffffff;
    if(n % m == 0) { printf("0\n"); return ; }
    if(K == 1){ printf("-1\n"); return ; }
    ll tmp1 = 0;
    while(1)
    {
        ll L = n, R = n, tmp2 = 0;
        if (n % m == 0) ans = min(ans, tmp1);
        if(n == 0)
        {
            ans = min(ans, tmp1);
            break;
        }
        while(1)
        {
            tmp2 += a;
            L = L * K, R = R * K + (K - 1);
            if(L % m == 0 || R % m == 0 || ((R / m) > (L / m)))
            {
                ans = min(ans, tmp1 + tmp2);
                break;
            }
        }
        n /= K, tmp1 += b;
    }
    // printf("%lld\n", ans);
    write(ans);
    putchar('\n');
}

int main()
{
    int T = read();
    while(T--) solve();
    return 0;
}

M 计算几何,单调性

考虑对于题面中要求的合法多边形 \(ABC\) 拆成 \(\triangle ABC\) 与多边形 \(B\rightarrow C\)

然后考虑顺序枚举距离的边数为 \(k\)\(BC\),发现每次整体后移一个点到 \(B'C'\),对整个图形的面积的影响仅为删掉了 \(\triangle BB'C\) 加上了 \(\triangle B'CC'\),可以很简单地维护。

此时 \(A\) 的取点范围是一个区间,由简单的几何常识容易发现在该区间上 \(S_{\triangle ABC}\) 显然是一个单峰函数,且极值点随 \(BC\) 的右移一定单调不减,于是可以三分,或在维护 \(BC\) 的同时单调指针后移维护。

注意这题精度要求很高,求三角形面积时应当使用向量叉乘,用海伦公式会死的非常惨。

code most by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
// #define double long double
const int N=1e6+5;
int n,k;
int T;
int x[N],y[N];
struct nod{
    int x,y;    
};
inline nod operator - (nod x,nod y) {return {x.x-y.x,x.y-y.y};}
inline int cross(nod x,nod y) {return x.x*y.y-x.y*y.x;}
inline int mian(nod x,nod y) {return fabs(cross(x,y));}
inline int calc(nod a,nod b,nod c) {return mian(a-b,a-c);}
int s(int a,int b,int c){
	return calc((nod){x[a], y[a]}, (nod){x[b], y[b]}, (nod){x[c], y[c]});
}
signed main(){
	// freopen("1.txt", "r", stdin);
	std::ios::sync_with_stdio(0), std::cin.tie(0);
	
	cin>>T;
	while(T--){
		cin>>n>>k;
		for(int i=1;i<=n;++i){
			cin>>x[i]>>y[i];
		}
		for(int i=n+1;i<=2*n;++i) x[i]=x[i-n],y[i]=y[i-n];
		int S=0,now=s(1,k+1,k+2),ans=0;
		int a=k+2;
		for(int i=2;i<=k;++i){
			S+=s(1,i,i+1);
		}
		while(a + 1 <= 2 * n && s(1,k+1,a+1) >now) now=s(1,k+1,a+1),++a;
		ans=max(ans,S+now);
		for(int i=2;i<=n+1;++i){
			S-=s(i-1,i,k+i-1);
			S+=s(i,k+i-1,k+i);

			while (a < i + k + 1) ++ a; //保证点的位置合法
			now = s(i, k + i, a);
			while(a + 1 <= 2 * n && s(i,k+i,a+1) >now) now=s(i,k+i,a+1),++a;

			ans=max(ans,S+now);
		}
		cout << fixed << setprecision(10) << 0.5 * ans << "\n";
		// printf("%.10lf\n",ans);
	}
	return 0;
}
/*
1
7 5
-80 -88
-79 -84
-78 -78
-79 -72
-100 -98
-93 -100
-84 -93

(-80, -88)
(-79, -84)
(-78, -78)
(-79, -72)
(-100, -98)
(-93, -100)
(-84, -93)
*/

J 二进制,连通性问题,并查集

考虑合法的路径的权值的按位与 \(V'\) 有什么性质:

  • 若有最高位的 1 大于 \(V\) 的最高位的 1,则一定合法;
  • 否则,存在某二进制位 \(i\),使得 \(V'\)\(i\) 位为 1,\(V\)\(i\) 位为 0,高于 \(i\) 位的部分两者完全相同。

若某条路径的 \(V'\) 符合上述情况中的任意一种,则该路径合法。发现上述情况数量至多仅有 60 种,且每种情况实际上钦定了 \(V'\) 中某些二进制位上一定为 1,也即所有可以经过的边的权值对应二进制位上一定为 1,这些二进制位可以很容易地通过位运算求得。

然后考虑枚举每种情况对应的合法的边集并使用并查集维护连通性,查询时仅需枚举上述所有情况,检查 \(u, v\) 在对应的并查集上是否连通即可,若连通则询问合法。

总时间复杂度 \(O((n+m)\log v + q)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 1e5 + 10;
const int kM = 5e5 + 10;
//=============================================================
int n, m, q;
LL V;
struct Edge {
  int u, v;
  LL w;
} edge[kM];
int fa[80][kN];
//=============================================================
int find(int id_, int x_) {
  return (fa[id_][x_] == x_) ? (x_) : (fa[id_][x_] = find(id_, fa[id_][x_])); 
}
void merge(int id_, int x_, int y_) {
  int fx = find(id_, x_), fy = find(id_, y_);
  if (fx == fy) return ;
  fa[id_][fx] = fy;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> m >> q >> V;
  for (int i = 1; i <= m; ++ i) {
    int u, v; LL w; std::cin >> u >> v >> w;
    edge[i] = (Edge) {u, v, w};
  }
  for (LL i = 0; i < 60; ++ i) {
    for (int j = 1; j <= n; ++ j) fa[i][j] = j;

    LL nowV = (1ll << i);
    if (nowV < V && (nowV & V) > 0ll) continue;
    if (nowV < V) nowV |= (V - ((nowV - 1) & V));
    for (int j = 1; j <= m; ++ j) {
      if ((edge[j].w & nowV) == nowV) {
        merge(i, edge[j].u, edge[j].v);
      }
    }
  }

  while (q --) {
    int u, v, ans = 0; std::cin >> u >> v;
    for (int i = 0; i < 60; ++ i) {
      int fu = find(i, u), fv = find(i, v);
      if (fu == fv) {
        ans = 1;
        break;
      }
    }
    std::cout << (ans ? "Yes" : "No") << "\n";
  }
  return 0;
}

K 贪心 or DP,结论,构造

我去大神 dztlb 写的什么东西看不懂。然而赛时少写一个字符没过真是我活爹啊。

题解做法是考虑 DP。

code by dztlb:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n,k,T;
char s[N],a[N];
int cnt,l[N],r[N];
bool check(int K){
	for(int i=2;i<n;++i){
		a[i]=s[i];
		if(a[i]=='?') a[i]='0';
	}
	for(int i=1;i<=cnt+5;++i) l[i]=r[i]=0;
	cnt=1;
	for(int i=1;i<=n;++i){
		if(a[i]=='1'){
//			cout<<i<<' '<<cnt<<' '<<l[cnt]<<' '<<r[cnt]<<endl;
			if(l[cnt]){
				r[cnt]=i;
				if(r[cnt]-l[cnt]>1){
					++cnt;l[cnt]=i;
				}else l[cnt]=i,r[cnt]=0;
			}else l[cnt]=i;
		}
		if(s[i]=='0'){
			l[cnt]=r[cnt]=0;
		}
	}
	--cnt;
	for(int i=1;i<=cnt;++i){
//		cout<<l[i]<<' '<<r[i]<<endl;
		for(int j=l[i];j<=r[i];++j){
			a[j]='1';
		}
	}
	for(int i=1;i<n;++i){
//		cout<<a[i];
		if(a[i]!=a[i+1]) --K;
	}
//	cout<<endl;
	if(K==0) return 1;
	if(K<0) return 0;
	for(int i=1;i<=cnt;++i){
		for(int j=l[i]+1;j<=r[i]-1;++j){
			a[j]='0';
		}
		K-=2;
		if(K==0) return 1;
	}
	for(int i=n-1;i>=2;--i){
		if(s[i]=='?'&&a[i+1]=='0'&&a[i-1]=='0'){
			a[i]='1'; K-=2;
		}
		if(K==0) return 1;
	}
	return 0;
}
signed main(){
	cin>>T;
	while(T--){
		cin>>n>>k;
		scanf("%s",s+1);
		for(int i=1;i<=n;++i) a[i]=s[i];
		if(k%2==0){
			if(s[1]=='?') a[1]='0';
			if(s[n]=='?') a[n]='0';
			if(a[1]=='0'&&a[n]=='0'){
				if(check(k)){
					for(int i=1;i<=n;++i) cout<<a[i];
					cout<<'\n';
					continue;
				}
			}
			if(s[1]=='?') a[1]='1';
			if(s[n]=='?') a[n]='1';
			if(a[1]=='1'&&a[n]=='1'){
//				cout<<"!!!\n";
				if(check(k)){
					for(int i=1;i<=n;++i) cout<<a[i];
					cout<<'\n';
					continue;
				}
			}
		}else{
			if(s[1]=='?') a[1]='0';
			if(s[n]=='?') a[n]='1';
			if(a[1]=='0'&&a[n]=='1'){
				if(check(k)){
					for(int i=1;i<=n;++i) cout<<a[i];
					cout<<'\n';
					continue;
				}
			}
			if(s[1]=='?') a[1]='1';
			if(s[n]=='?') a[n]='0';
			if(a[1]=='1'&&a[n]=='0'){
				if(check(k)){
					for(int i=1;i<=n;++i) cout<<a[i];
					cout<<'\n';
					continue;
				}
			}
		}
		puts("Impossible");
	}
	return 0;
}

F 线段树优化 DP

赛时写了个假 DP 哈哈。

和这题非常类似的一题:https://www.luogu.com.cn/problem/P6146

考虑按照某种升序选择线段并在此过程中计算已考虑的线段的合法方案数之和。加入一条线段时,仅需考虑这条线段是否与之前的同色线段相交,是否可以继承之前的贡献,则将线段按右端点排序。

考虑记 \(f_{i}\) 表示加入到第 \(i\) 条线段,且钦定\(i\) 条线段必选,考虑枚举加入的最后一条颜色不同的线段是哪条,则在此之后的同色线段可以任意选择,则有:

\[f_{i} = \sum_{1\le j< i, r_j< l_i, \operatorname{color}_{j}\not= \operatorname{color}_{j}} f_{j} \times 2^{g_{l_{i}, r_{i}, c}} \]

上式中系数 \(g_{l_i, r_i, c}\) 表示被区间 \([l_i, r_i]\) 完整包含的颜色也为 \(c\) 的线段数量,代表这些线段可以任意选择。

答案即为:

\[\sum_{i=1}^{n} f_i \]

上式中合法的 \(j\) 是一个前缀的形式,其范围的上界 \(p_i\) 可以简单地二分求得;但是如果直接做维护系数 \(g\) 并转移显然是 \(O(n^2)\) 的。但是发现因为线段按右端点排序,每次加入一条线段 \([l_i, r_i]\) 后,对 \(g\) 的影响实际上即令 \(f_{1}\sim f_{p_i}\) 对应的系数 \(g\) 均乘 2,则转移实际上即查询一个带权前缀和,然后做单点插入、前缀乘 2。

于是考虑分别对 \(c=0/1\)\(f\) 分别维护线段树,支持单点修改、前缀乘 2
、查询前缀和,每次转移时仅需在另一种颜色对应的线段树上查询,即可快速转移。

总时间复杂度 \(O(n\log n)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
const LL p = 998244353;
//=============================================================
int n;
LL f[kN], g[kN];
struct Line {
  int l, r, c;
} line[kN];
std::vector<int> st[2];
//=============================================================
bool cmp(const Line &fir_, const Line &sec_) {
  return fir_.r < sec_.r;
}
const int kNode = kN << 2;
struct Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  LL sum[kNode], tag[kNode];
  void pushup(int now_) {
    sum[now_] = (sum[ls] + sum[rs]) % p;
  }
  void pushdown(int now_) {
    sum[ls] *= tag[now_], sum[ls] %= p;
    sum[rs] *= tag[now_], sum[rs] %= p;
    tag[ls] *= tag[now_], tag[ls] %= p;
    tag[rs] *= tag[now_], tag[rs] %= p;
    tag[now_] = 1; 
  }
  void build(int now_, int L_, int R_) {
    tag[now_] = 1, sum[now_] = 0;
    if (L_ == R_) return ;
    build(ls, L_, mid), build(rs, mid + 1, R_);
  }
  void insert(int now_, int L_, int R_, int p_, LL val_) {
    if (L_ == R_) {
      sum[now_] = val_;
      return ;
    }
    if (tag[now_] > 1) pushdown(now_);
    if (p_ <= mid) insert(ls, L_, mid, p_, val_);
    else insert(rs, mid + 1, R_, p_, val_);
    pushup(now_); 
  }
  void modify(int now_, int L_, int R_, int r_) {
    if (R_ <= r_) {
      sum[now_] *= 2ll, sum[now_] %= p;
      tag[now_] *= 2ll, tag[now_] %= p;
      return ;
    }
    if (tag[now_] > 1) pushdown(now_);
    modify(ls, L_, mid, r_);
    if (r_ > mid) modify(rs, mid + 1, R_, r_);
    pushup(now_);
  }
  LL query(int now_, int L_, int R_, int r_) {
    if (R_ <= r_) return sum[now_];
    if (tag[now_] > 1) pushdown(now_);
    if (r_ <= mid) return query(ls, L_, mid, r_);
    return (sum[ls] + query(rs, mid + 1, R_, r_)) % p;
  }
  #undef ls
  #undef rs
  #undef mid
} seg[2];
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  int T; std::cin >> T;
  while (T --) {
    std::cin >> n;
    for (int i = 1; i <= n; ++ i) {
      int l, r, c; std::cin >> l >> r >> c;
      line[i] = (Line) {l, r, c};
    }
    std::sort(line + 1, line + n + 1, cmp);

    f[0] = g[0] = 1;
    for (int i = 0; i <= 1; ++ i) {
      seg[i].build(1, 0, n);
      seg[i].insert(1, 0, n, 0, 1);
      st[i].clear(), st[i].push_back(0);
    }
    for (int i = 1; i <= n; ++ i) {
      int j = 0, c = line[i].c;
      for (int l = 1, r = st[c ^ 1].size() - 1; l <= r; ) {
        int mid = (l + r) >> 1;
        if (line[st[c ^ 1][mid]].r <= line[i].l - 1) {
          j = st[c ^ 1][mid];
          l = mid + 1;
        } else {
          r = mid - 1;
        }
      }

      f[i] = seg[c].query(1, 0, n, j);
      seg[c ^ 1].insert(1, 0, n, i, f[i]);
      seg[c].modify(1, 0, n, j);
      g[i] = (g[i - 1] + f[i]) % p;

      st[c].push_back(i);
    }
    std::cout << g[n] << "\n";
  } 
  return 0;
}
/*
1
4
1 2 0
2 3 1
3 4 0
4 5 1
*/

写在最后

学到了什么:

  • M:海伦公式精度损失太大不不不不可用;
  • E:表达式上界的分析;
  • J:拆位考虑二进制运算的大小比较。
posted @ 2024-09-29 11:07  Luckyblock  阅读(35)  评论(0编辑  收藏  举报