2024“钉耙编程”中国大学生算法设计超级联赛(1)

写在前面

补题地址:https://acm.hdu.edu.cn/listproblem.php?vol=65 7433 ~ 7444

以下按个人难度向排序。

我草我线段树分治为啥过不去!!!

7440

dztlb 大神秒了,我看都没看。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=40005;
int n,k,t,ans,sum; 
void solve(int x){
	int d=1;
	for(int i=0;i<k;++i){
		if(x&(1ll<<i)){
			d*=2;	
		}
	}
	ans+=d*sum;
}
signed main(){
	cin>>t;
	while(t--){
		ans=0,sum=0;
		cin>>n>>k;
		for(int i=(1ll<<k)-1;i>=0;--i){
			int tmp=1;
			for(int j=0;j<k;++j){
				if(((i>>j)&1)==0){
					tmp*=3;
				}
			}
			sum+=tmp;
		}
//		cout<<sum<<endl;
		for(int i=n;i;i=(i-1)&n){
			solve(i);
		}
		solve(0);
		cout<<ans<<'\n';
	}
	return 0;
}
/*
1
5
10 2 31 44 73

*/

7434

DP。

发现 \(nk\) 可过,则有显然的二维 DP,设 \(f_{i, j}\) 表示使用前 \(i\) 次操作获得 \(j\) 颗星星的最小代价之和,初始化 \(f_{i, j} = +\infin\)\(f_{0, 0} = 0\),则有转移:

\[f_{i, j} = \min\left( f_{i - 1, j}, f_{i - 1, j - 1} + a, f_{i - 1, j - 2} + b, f_{i - 1, j - 3} + c, f_{i - 1, j - 4} + d\right) \]

答案即为 \(f_{n, k}\)。总时间复杂度 \(O(nk)\) 级别。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const LL kInf = 1e18 + 2077;
const int kN = 1010;
//=============================================================
int n, k;
LL ans, cost[kN][5], f[kN][4 * kN];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0'); 
  return f * w;
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  int T; std::cin >> T;
  while (T --) {
    n = read(), k = read();

    for (int i = 0; i <= n; ++ i) {
      for (int j = 0; j <= k; ++ j) {
        f[i][j] = kInf;
      }
    }
    f[0][0] = 0;
    
    for (int i = 1; i <= n; ++ i) {
      for (int j = 1; j <= 4; ++ j) cost[i][j] = read();
      for (int j = 0; j <= k; ++ j) {
        f[i][j] = std::min(f[i][j], f[i - 1][j]);
        for (int l = 1; l <= std::min(j, 4); ++ l) {
          f[i][j] = std::min(f[i][j], f[i - 1][j - l] + cost[i][l]);
        }
      }
    }

    printf("%lld\n", f[n][k]);
  }
  return 0;
}

7433

哈希。

发现 \([A]\) 即为 \(A\)\(|A|\) 种循环移位,即字符串 \(A+A\)\(n\) 种长度为 \(|A|\) 的连续子串。于是考虑对 \(A + A, B\) 分别进行字符串哈希,记录上述子串的哈希值,然后枚举 \(B\) 中长度为 \(|A|\) 的子串检查是否出现即可。

总时间复杂 \(O(\sum |A| + |B|)\) 级别。

//知识点:二分答案,Hash
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kMaxn = 3e6 + 10;
const LL kMod1 = 998244353;
const LL kMod2 = 1e9 + 9;
const LL kBase = 1145141;
//=============================================================
int n1, n2, ans;
char s1[kMaxn], s2[kMaxn];
LL pow1[kMaxn], pow2[kMaxn];
LL has11[kMaxn], has12[kMaxn], has21[kMaxn], has22[kMaxn];
std::set <std::pair<LL,LL> > yes;
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void Prepare() {
  scanf("%s", s1 + 1);
  scanf("%s", s2 + 1);
  n1 = strlen(s1 + 1);
  n2 = strlen(s2 + 1);
  
  for (int i = 1; i <= n1; ++ i) s1[n1 + i] = s1[i];
  n1 = 2 * n1;
  s1[n1 + 1] = '\0';
  
  pow1[0] = pow2[0] = 1;
  for (int i = 1; i < std::max(n1, n2); ++ i) {
    pow1[i] = pow1[i - 1] * kBase % kMod1;
    pow2[i] = pow2[i - 1] * kBase % kMod2;
  }
  for (int i = 1; i <= n1; ++ i) {
    has11[i] = (has11[i - 1] * kBase + s1[i]) % kMod1;
    has12[i] = (has12[i - 1] * kBase + s1[i]) % kMod2;
  }
  for (int i = 1; i <= n2; ++ i) {
    has21[i] = (has21[i - 1] * kBase + s2[i]) % kMod1;
    has22[i] = (has22[i - 1] * kBase + s2[i]) % kMod2;
  }
}
void solve() {
  ans = 0;

  yes.clear();
  for (int l = 1, r = n1 / 2; r <= n1; ++ l, ++ r) {
    LL now_has11 = ((has11[r] - has11[l - 1] * pow1[r - l + 1] % kMod1) + kMod1) % kMod1;
    LL now_has12 = ((has12[r] - has12[l - 1] * pow2[r - l + 1] % kMod2) + kMod2) % kMod2;
    yes.insert(std::make_pair(now_has11, now_has12));
  }
  for (int l = 1, r = n1 / 2; r <= n2; ++ l, ++ r) {
    LL now_has21 = ((has21[r] - has21[l - 1] * pow1[r - l + 1] % kMod1) + kMod1) % kMod1;
    LL now_has22 = ((has22[r] - has22[l - 1] * pow2[r - l + 1] % kMod2) + kMod2) % kMod2;
    if (yes.count(std::make_pair(now_has21, now_has22))) ++ ans;
  }
}
//=============================================================
int main() {
  // freopen("A.txt", "r", stdin);
  int T = read();
  while (T --) {
    Prepare();
    solve();
    printf("%d\n", ans);
  }
  return 0;
}
/*
opawmfawklmiosjcas1145141919810asopdfjawmfwaiofhauifhnawf
opawmdawlmioaszhcsan1145141919810bopdjawmdaw
*/

7435

dsu on tree。

挺板的。

发现计算 \(f(u, v)\) 时仅需考虑两个权值间的大小关系即可,和树的形态并无关系。则对于子树的答案 \(ans_i\),仅需考虑子树中节点的权值构成的多重集 \(S\) 即可。先处理对于某个子树的单次询问。考虑不断向上述多重集 \(S\) 中加入权值 \(x\),并求得新增的 \(\sum_{y\in S} f(x, y)\) 的贡献,发现仅需讨论 \(x\)\(y\) 的相对关系大小:

  • \(x = y\)\(f(x, y) = 0\)
  • \(x < y\)\(f(x, y) = y(y - x) = y^2 - xy\),则所有 \(y\) 的贡献之和为 \(\left(\sum_y y^2\right) - x\left(\sum_y y\right)\)
  • \(x > y\)\(f(x, y) = x(x - y) = x^2 - xy\),则所有 \(y\) 的贡献之和为 \(x^2\left(\sum_y 1\right) + x\left(\sum_y y\right)\)

发现上述 \(\sum_y 1\)\(\sum_y y\)\(\sum_y y^2\) 即为某段权值区间内某个权值贡献分别为 1、\(y\)\(y^2\) 时,该权值区间的贡献之和。很容易使用权值树状数组维护。则单次子树询问操作仅需按任意顺序枚举所有权值,并不断把他们加入多重集,在此过程中使用树状数组维护新增贡献即可。

又本题要求所有子树的贡献,发现将每个节点加入多重集的操作是独立的,于是直接套个 dsu on tree 就完了。

模数为 \(2^{64}\) 直接自然溢出啥事儿没有!

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

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 5e5 + 10;
const int kM = kN << 1;
const int kLim = 1e6;
//=============================================================
int n, a[kN], edgenum, head[kN], v[kM], ne[kM];
int dfnnum, node[kN], L[kN], R[kN], sz[kN], son[kN];
unsigned LL nowans, ans[kN];
//=============================================================
const int kNode = kM;
#define lowbit(x) ((x)&(-x))
struct Bit {
  int lim; 
  unsigned LL t[kNode];
  void init(int lim_) {
    lim = lim_;
  }
  void insert(int pos_, unsigned LL val_) {
    for (int i = pos_; i <= lim; i += lowbit(i)) {
      t[i] += val_;
    }
  }
  LL sum(int pos_) {
    LL ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) ret += t[i];
    return ret;
  }
  LL query(int l_, int r_) {
    if (l_ > r_) return 0;
    return sum(r_) - sum(l_ - 1);
  }
} bit[3];
void Add(int u_, int v_) {
  v[++ edgenum] = v_;
  ne[edgenum] = head[u_];
  head[u_] = edgenum;
}
void add(int u_) {
  unsigned LL v = a[u_];
  nowans += v * (bit[0].sum(v - 1) * v - bit[1].sum(v - 1));
  nowans += bit[2].query(v + 1, kLim) - v * bit[1].query(v + 1, kLim);

  bit[0].insert(v, 1);
  bit[1].insert(v, v);
  bit[2].insert(v, v * v);
}
void del(int u_) {
  unsigned LL v = a[u_];
  nowans -= v * (bit[0].sum(v - 1) * v - bit[1].sum(v - 1));
  nowans -= bit[2].query(v + 1, kLim) - v * bit[1].query(v + 1, kLim);

  bit[0].insert(v, -1);
  bit[1].insert(v, -v);
  bit[2].insert(v, -v * v);
}
void Dfs1(int u_, int fa_) {
  L[u_] = ++ dfnnum;
  node[dfnnum] = u_;
  sz[u_] = 1;
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_) continue;
    Dfs1(v_, u_);
    sz[u_] += sz[v_];
    if (sz[v_] > sz[son[u_]]) son[u_] = v_;
  }
  R[u_] = dfnnum;
}
void Dfs2(int u_, int fa_, bool son_) {
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_ || v_ == son[u_]) continue;
    Dfs2(v_, u_, 0);
  }
  if (son[u_]) Dfs2(son[u_], u_, 1);
  for (int i = head[u_]; i; i = ne[i]) {
    int v_ = v[i];
    if (v_ == fa_ || v_ == son[u_]) continue;
    for (int j = L[v_]; j <= R[v_]; ++ j) add(node[j]);
  }
  add(u_);
  ans[u_] = nowans;
  if (!son_) {
    for (int i = L[u_]; i <= R[u_]; ++ i) {
      del(node[i]);
    }
  }
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i < n; ++ i) {
    int u_, v_; std::cin >> u_ >> v_;
    Add(u_, v_), Add(v_, u_);
  }
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];

  for (int i = 0; i < 3; ++ i) bit[i].init(kLim);
  Dfs1(1, 1), Dfs2(1, 0, 0);

  unsigned LL output = 0;
  for (int i = 1; i <= n; ++ i) output ^= 2ll * ans[i];
  std::cout << output << "\n";
  return 0;
}

7444

数学,乱搞。

dztlb 大神写的,场上我看都没看。

给定矩形数量并不太多,于是考虑将横纵坐标离散化,有用的坐标数量仅有 \(O(n^2)\) 级别,对它们进行二位前缀和即可快速求出每个区域被多少个矩形覆盖。记 \(s_j\) 表示恰好被 \(j\) 个给定矩形覆盖的区域的面积之和。

然后考虑枚举选出的矩形数量 \(i\),再考虑枚举有贡献的区域共被矩形覆盖的数量 \(j\)。显然当 \(n-j<i\) 时这样的区域至少会被 \(i\) 个矩形中一个覆盖,则贡献即为 \(s_j\);当 \(n-j>i\) 时,考虑取补集,求这样的区域不被 \(i\) 个矩形任意一个覆盖的概率,则期望覆盖面积并 \(ans_i\) 为:

\[ans_i = \sum_{j = 1}^{n} \left(1 - \dfrac{{{n - j}\choose{i}}}{{n\choose i}} \right) \times s_{j} \]

预处理下杨辉三角即可。总时间复杂度 \(O(n^2)\) 级别。

赛时 dztlb 大神的做法是把给定的点坐标转化成格子的坐标了,离散化后有 \(4n^2\) 的点所以需要用 short 卡下空间呃呃、、、实际上直接用点的坐标就行了场上还因此爆吃两发红温唐唐唐唐唐完了

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=4105;
const int mod=998244353;
int n;
short ma[8008][8008];
int C[2005][2005];
struct node{
	int a,b,c,d;
}p[N];
int a[N*8],b[N*8],aid[N*8],bid[N*8];
int t1,t2;
int ans[N],sum[N];
int qpow(int x,int y){
	int ans=1;
	while(y){
		if(y&1) ans=ans*x%mod;
		x=x*x%mod;
		y>>=1;
	} return ans;
}
signed main(){
	cin>>n;
	C[0][0]=1;
	for (int i = 1; i <= n; ++ i) {
		C[i][0]=1;
		C[i][i]=1;
		for (int j=1;j<=i-1;++j) C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;
	}
	for(int i=1;i<=n;++i){
		cin>>p[i].a>>p[i].b>>p[i].c>>p[i].d;
		p[i].a+=5;
		p[i].b+=5;
		p[i].c+=5;
		p[i].d+=5;
		p[i].c--,p[i].d--;
		a[++t1]=p[i].a,a[++t1]=p[i].c;
		a[++t1]=p[i].a-1,a[++t1]=p[i].c+1;
		b[++t2]=p[i].b,b[++t2]=p[i].d;	
		b[++t2]=p[i].b-1,b[++t2]=p[i].d+1;	
	}
	sort(a+1,a+1+t1);
	sort(b+1,b+1+t2);
	int l1=unique(a+1,a+1+t1)-(a+1);
	int l2=unique(b+1,b+1+t2)-(b+1);
	for(int i=1;i<=n;++i){
		p[i].a=lower_bound(a+1,a+l1+1,p[i].a)-a;
		p[i].c=lower_bound(a+1,a+l1+1,p[i].c)-a;
		p[i].b=lower_bound(b+1,b+l2+1,p[i].b)-b;
		p[i].d=lower_bound(b+1,b+l2+1,p[i].d)-b;
		ma[p[i].a][p[i].b]++;
		ma[p[i].a][p[i].d+1]--;
		ma[p[i].c+1][p[i].b]--;
		ma[p[i].c+1][p[i].d+1]++;
	}
	for(int i=1;i<=l1;++i){
		for(int j=1;j<=l2;++j){
			ma[i][j]+=ma[i-1][j]+ma[i][j-1]-ma[i-1][j-1];
			if(ma[i][j]>=1) {
				
				sum[ma[i][j]]+=(a[i+1]-a[i])*(b[j+1]-b[j])%mod,sum[ma[i][j]]%=mod;
				
				}
			
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=n;++j){
			if(n-j>=i) ans[i]+=sum[j]*((C[n][i]-C[n-j][i]+mod)%mod)%mod;
			else ans[i]+=sum[j]*(C[n][i])%mod;
			ans[i]%=mod;
		}
		cout<<ans[i]*qpow(C[n][i],mod-2)%mod<<'\n';
	}
	return 0;
}
/*
3
1 1 2 2
3 3 4 4
1 1 4 4
*/

7438

DP,结论

我草这题好玩啊,这个转化确实有趣

发现贡献为本质不同子序列出现次数的立方之和,等价于选择三个子序列使它们相同的方案数。证明可考虑设所有本质不同子序列的集合为 \(S\),则选择三个子序列 \(a, b, c\) 使它们本质相同的方案数即为:

\[\sum_{a\in S}\sum_{b\in S}\sum_{c\in S} [a = b = c] \]

上式实际含义为:一组子序列 \(a=b=c\) 的贡献即为 \(a = b = c\) 这种情况出现次数的立方。发现有 \(a = b = c\) 当且仅当 \(a, b, c\) 各元素对应位置相同,其数量即为这一种本质子序列出现次数。则上式等价于本质不同子序列出现次数的立方之和,即为题目所求。

问题转化为经典的 DP 问题,设 \(f_{i, j, k}\) 表示选择的第一个序列结尾为 \(a_i\),第二个序列结尾为 \(a_j\),第三个序列结尾为 \(a_k\) 时合法的方案数。初始化 \(f_{0, 0, 0} = 1\),转移时考虑枚举三个子序列各自的前驱,则有:

\[f_{i, j, k} = \begin{cases} \sum\limits_{0\le i'<i} \sum\limits_{0\le j'<j} \sum\limits_{0\le k'<k} f_{i', j', k'} &(a_i = a_j = a_k)\\ 0 &\text{otherwise} \end{cases}\]

答案即为 \(\sum_{a_i = a_j = a_k} f_{i, j, k}\)

直接做是 \(O(n^6)\) 的过不了,但是这个转移的形态太优美了,相当于做了一个三维的前缀和,直接三维前缀和优化一下记 \(g_{i, j, k} = \sum\limits_{0\le i'\le i} \sum\limits_{0\le j'\le j} \sum\limits_{0\le k'\le k} f_{i', j', k'}\) 即可。时间复杂度即降为 \(O(n^3)\) 级别。

下面放两种实现方法,第一种实现方法中各状态如上所示,第二种实现方法为了少写一点初始化,修改状态 \(f_{i, j, k}\) 表示选择的第一个序列结尾为 \(a_{i - 1}\),第二个序列结尾为 \(a_{j - 1}\),第三个序列结尾为 \(a_{k - 1}\) 时合法的方案,并没有显式地设状态 \(g\) 而是加完贡献后直接在 \(f\) 上做了前缀和。

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 250 + 10;
const LL p = 998244353;
//=============================================================
int n, a[kN];
LL ans, f[kN][kN][kN], g[kN][kN][kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];

  f[0][0][0] = g[0][0][0] = 1;
  for (int i = 0; i <= n; ++ i) {
    for (int j = 0; j <= n; ++ j) {
      g[i][j][0] = g[i][0][j] = g[0][i][j] = 1;
    }
  }

  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      for (int k = 1; k <= n; ++ k) {
        if (a[i] == a[j] && a[i] == a[k] && a[j] == a[k]) {
          f[i][j][k] = g[i - 1][j - 1][k - 1];
          ans += f[i][j][k], ans %= p;
        }
        g[i][j][k] = f[i][j][k];
        g[i][j][k] += (g[i - 1][j][k] + g[i][j - 1][k] + g[i][j][k - 1]) % p, g[i][j][k] %= p;
        g[i][j][k] -= (g[i - 1][j - 1][k] + g[i - 1][j][k - 1] + g[i][j - 1][k - 1]) % p, g[i][j][k] %= p; 
        g[i][j][k] += p, g[i][j][k] %= p;
        g[i][j][k] += g[i - 1][j - 1][k - 1], g[i][j][k] %= p;
      }
    }
  }
  std::cout << ans << "\n";
  return 0;
}
/*
3
1 1 1

2
1 1 
*/
//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 250 + 10;
const LL p = 998244353;
//=============================================================
int n, a[kN];
LL ans, f[kN][kN][kN];
//=============================================================
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i];

  f[0][0][0] = 1;
  for (int i = 1; i <= n; ++ i) {
    for (int j = 1; j <= n; ++ j) {
      for (int k = 1; k <= n; ++ k) {
        f[i][j][k] += (f[i - 1][j][k] + f[i][j - 1][k] + f[i][j][k - 1]) % p;
        f[i][j][k] -= (f[i - 1][j - 1][k] + f[i - 1][j][k - 1] + f[i][j - 1][k - 1]) % p; 
        f[i][j][k] = (f[i][j][k] + p) % p;
        f[i][j][k] += f[i - 1][j - 1][k - 1], f[i][j][k] %= p;
        if (a[i] == a[j] && a[i] == a[k] && a[j] == a[k]) {
          ans += f[i][j][k], ans %= p;
          f[i + 1][j + 1][k + 1] += f[i][j][k];
          f[i + 1][j + 1][k + 1] %= p;
        }
      }
    }
  }
  std::cout << ans << "\n";
  return 0;
}
/*
3
1 1 1
*/

7436

线段树分治,并查集。

场上脑子一抽想出了这个在维护并查集同时打 tag 的 trick,本场最智慧的一集,可惜没调出来输输输。

每条边在时间段内有效,一眼典中典之线段树分治+可撤销并查集。

考虑线段树每个叶节点代表的时刻的贡献,若此时为时刻 \(i\),则此时包含节点 1 的连通块内的所有节点的 \(ans\) 均应加 \(i\)。由于在此过程中并查集维护联通块,考虑在 1 所在联通块的祖先上打一个 \(+i\) 的标记,表示该联通块内所有节点贡献均 \(+i\)

又并查集合并时,一定是先查询两联通块的祖先,然后在两祖先间连边,即标记的下放关系与祖先间连边的关系是完全一致的,则仅需在并查集断边时,不断地将作为父亲的祖先的标记下放即可;同时为了避免贡献重复,考虑到递归连边后的回溯时一定会对称地断边,则应当在连边时令作为儿子的祖先,减去作为父亲的祖先的贡献。

线段树分治板子稍微改改就行了,总时间复杂度 \(O(n\log^2 n + m\log n)\) 级别。

hduoj 什么老爷机这么慢妈的跑了十遍就过了一遍呃呃,网上找了份赛时过了的代码也是这样,所以要是下面代码 T 了大概不是我的问题。

//知识点:线段树分治,可撤销并查集
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 6e5 + 10;
//=============================================================
struct Edge {
  int u, v;
} edge[kN];
struct Stack {
  int u, v, fa, sz;
} st[21 * kN];
int n, m, k, top, fa[kN], sz[kN];
LL tag[kN];
//=============================================================
int find(int x_) {
  while (x_ != fa[x_]) x_ = fa[x_];
  return x_;
}
void merge(int u_, int v_) {
  int fu = find(u_), fv = find(v_);
  if (sz[fu] > sz[fv]) std::swap(fu, fv);

  st[++ top] = (Stack) {fu, fv, fa[fu], sz[fu]};
  if (fu != fv) {
    fa[fu] = fv, sz[fv] += sz[fu], sz[fu] = 0;
    tag[fu] -= tag[fv];
  }
}
void restore() {
  Stack now = st[top];
  if (now.u != now.v) {
    int fu = now.u, fv = now.v;
    fa[fu] = now.fa, sz[fv] -= now.sz, sz[fu] = now.sz;
    tag[fu] += tag[fv];
  }
  top --;
}
namespace Seg {
  #define ls (now_<<1)
  #define rs (now_<<1|1)
  #define mid ((L_+R_)>>1)
  std::vector <int> t[kN << 2];
  void modify(int now_, int L_, int R_, int l_, int r_, int val_) {
    if (l_ <= L_ && R_ <= r_) {
      t[now_].push_back(val_);
      return ;
    }
    if (l_ <= mid) modify(ls, L_, mid, l_, r_, val_);
    if (r_ > mid) modify(rs, mid + 1, R_, l_, r_, val_);
    return ;
  }
  void solve(int now_, int L_, int R_) {
    for (auto x: t[now_]) merge(edge[x].u, edge[x].v);
    if (L_ == R_) {
      tag[find(1)] += L_;
      for (int i = t[now_].size(); i; -- i) restore();
      return ;
    }
    solve(ls, L_, mid), solve(rs, mid + 1, R_);
    for (int i = t[now_].size(); i; -- i) restore();
  }
}
//=============================================================
int main() {
  // freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n >> m;
  for (int i = 1; i <= m; ++ i) {
    int a_, b_, l_, r_; std::cin >> a_ >> b_ >> l_ >> r_;
    edge[i].u = a_, edge[i].v = b_;
    Seg::modify(1, 1, n, l_, r_, i);
  }

  for (int i = 1; i <= n; ++ i) fa[i] = i, sz[i] = 1, tag[i] = 0;
  Seg::solve(1, 1, n);

  LL output = 0;
  for (int i = 1; i <= n; ++ i) output ^= tag[i];
  std::cout << output;
  return 0;
}
/*
5 7
2 3 1 4
3 4 1 4 
4 5 1 4 
1 2 1 1 
1 3 2 2 
1 4 3 3 
1 5 4 4

5 5
1 2 1 4
2 3 2 4
3 4 3 4
4 5 4 5
5 1 1 3
*/

7439

三维偏序。

实际唐氏计数题。场上光盯着线段树分治红温了妈的这题应该写下的。

给定的图是张竞赛图,发现竞赛图任意取出三个节点的子图,要么是三元环要么其中一个节点入度为 2。于是直接容斥一下,求选出三个节点的方案数减去每个节点位于使它入度为 2 的子图数即可。记入度为 \(\operatorname{in}\) 则答案即为:

\[{n\choose 3} - \sum_{i=1}^{n}{\operatorname{in}_i \choose 2} \]

又根据给定的建图方式,对于 \(i<j\) 存在有向边 \((i, j)\) 当且仅当 \((i<j) \land (f_i < f_j) \land (g_i < g_j)\),显然的三维偏序形式,cdq 里讨论一下每个点连出去的各种边的数量就行了。

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

//
/*
By:Luckyblock
*/
#include <bits/stdc++.h>
#define LL long long
const int kN = 2e5 + 10;
//=============================================================
int n, in[kN], in1[kN], out1[kN];
struct Point {
  int x, y, z;
} a[kN];
//=============================================================
bool cmpy(const Point& fir_, const Point &sec_) {
  return fir_.y < sec_.y;
}
namespace bit {
  #define lowbit(x) ((x)&(-x))
  const int kNode = kN;
  int lim, nowtime, tag[kNode], t[kNode];
  void init(int lim_) {
    lim = lim_;
    ++ nowtime;
  }
  void insert(int pos_, int val_) {
    for (int i = pos_; i <= lim; i += lowbit(i)) {
      if (tag[i] != nowtime) tag[i] = nowtime, t[i] = 0;
      t[i] += val_;
    }
  }
  int sum(int pos_) {
    int ret = 0;
    for (int i = pos_; i; i -= lowbit(i)) {
      if (tag[i] != nowtime) tag[i] = nowtime, t[i] = 0;
      ret += t[i];
    }
    return ret;
  }
  int query(int L_, int R_) {
    if (L_ > R_) return 0;
    return sum(R_) - sum(L_ - 1);
  }
}
void cdq(int L_, int R_) {
  if (L_ == R_) return ;
  int mid = (L_ + R_) >> 1;
  cdq(L_, mid), cdq(mid + 1, R_);
  std::sort(a + L_, a + mid + 1, cmpy);
  std::sort(a + mid + 1, a + R_ + 1, cmpy);
  
  bit::init(n);
  int l = L_, r = mid + 1;
  for (; r <= R_; ++ r) {
    for (; a[l].y < a[r].y && l <= mid; ++ l) bit::insert(a[l].z, 1);
    in1[a[r].x] += bit::sum(a[r].z - 1);
  }
  bit::init(n);
  l = mid, r = R_;
  for (; l >= L_; -- l) {
    for (; a[r].y > a[l].y && r > mid; -- r) bit::insert(a[r].z, 1);
    out1[a[l].x] += bit::query(a[l].z + 1, n);
  }
  return ;
}
//=============================================================
int main() {
  //freopen("1.txt", "r", stdin);
  std::ios::sync_with_stdio(0), std::cin.tie(0);
  std::cin >> n;
  for (int i = 1; i <= n; ++ i) a[i].x = i, in[i] = in1[i] = out1[i] = 0;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i].y;
  for (int i = 1; i <= n; ++ i) std::cin >> a[i].z;
  cdq(1, n);
  for (int i = 1; i <= n; ++ i) in[i] = in1[i] + (n - i) - out1[i];
  // for (int i = 1; i <= n; ++ i) std::cout << in1[i] << " " << out1[i] << " " << in[i] << "\n";
  // std::cout << "\n\n\n";
  LL ans = 1ll * n * (n - 1ll) / 2ll * (n - 2ll) / 3ll;
  for (int i = 1; i <= n; ++ i) ans -= 1ll * in[i] * (in[i] - 1) / 2ll;
  std::cout << ans << "\n";
  return 0;
}
/*
9
3 7 2 1 4 5 9 8 7
2 4 1 5 7 9 2 4 1
*/

7437

计数,概率

发现每个人取字符是随机的,实际上是虚假博弈题。则无平局时,显然每个人不失败的概率均为 \(\frac{1}{2}\),于是仅需考虑求平局概率即可。发现当个数为奇数的字符数量不小于 2 时一定不会出现平局,于是仅需讨论个数为奇数的数为 0/1 时的平局概率。

平局概率均可以通过组合意义得到,推式子实在不是我的强项要是场上遇到直接扔给队友了哈哈。详见:https://www.cnblogs.com/purplevine/p/18313459

写在最后

学到了什么:

  • 7436:线段树分治+维护联通块的套路。
  • 7438:一个元素贡献多次,转化成多个组合元素贡献一次。
  • 7444:有用的总数。
  • 7439:竞赛图的特殊性质。
  • 7437:并非博弈!两方不败概率相同考虑求平局数。

mad 小梓立牌怎么卖那么快大早上到就没货了付款之前抢完了佳代子也没拿到只能拿了个纱织输输输千年是明日奈太几把傻逼了就算换个尼禄上来也不至于这个人气、、、

posted @ 2024-07-20 00:01  Luckyblock  阅读(842)  评论(1编辑  收藏  举报