2025.08.04 HDU 多校ACM

contest

1004. 传送排序

tag: DP

我们可以发现,除了在序列的最前面插入,其他在一个数前面插入,都可以用在一个数后面插入去替代,而最前面插入可以通过在原序列的最前面加入一个 \(0\) 来处理

同时另外有一个小性质,就是显然任何一个序列最多只需要 \(n\) 次操作即可

然后我们就可以设计 \(DP\)

\(pos_i\) 表示编号为 \(i\) 的数在原序列中的下标

\(f_i\) 表示,编号为 \(i\) 的猫没有移动,排序完 \(1\sim i\) 的猫的位置需要的最少的操作次数

\[f_i = \min_{j = 0} ^ {i-1} \begin{cases} f_{i-1} & j == i-1\ \&\ pos_{i-1} < pos_i \\ f_{j}+(i-j) & j != i-1\ \&\ pos_j < pos_i\\ \inf & pos_j > pos_i \end{cases} \]

而这个式子的主要部分很特别 \(f_i = f_j+i-j\)

我们移项之后发现,我们需要比较的是 \(f_i -i\)\(f_j - j\) 的大小

我们发现,我们可以用树状数组来加速查询操作

具体地,我们每次在下标为 \(pos\) 处加入一个 \(f_i - i\),查询就是查询前缀最小值即可

最后的时间复杂度 \(O(n\log n)\)

code
#include<bits/stdc++.h>
using namespace std;
const int NN = 2e5 + 8,INF = 0x3f3f3f3f;
int T;
int n;
int a[NN],pos[NN];
int f[NN];

int b[NN];
inline int lowbit(int x){return x & (-x);}
inline void add(int x,int num){
	while(x <= n){
		b[x] = min(b[x],num);
		x += lowbit(x);
	}
}
inline int ask(int x){
	int ans = INF;
	while(x){
		ans = min(ans,b[x]);
		x -= lowbit(x);
	}
	return ans;
}
int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	cin >> T;
	while(T--){
		cin >> n;
		for(int i = 1; i <= n; ++i){
			cin >> a[i];
			b[i] = INF;
			pos[a[i]] = i;
		}
		for(int i = 1; i <= n; ++i){
			if(pos[i-1] < pos[i]) f[i] = f[i-1];
			else f[i] = i;
			int ak = ask(pos[i]);
			if(f[i]-i > ak) f[i] = ak + i;
			add(pos[i],f[i]-i);
		}
		int ans = min(n,f[n]);
		for(int i = 1; i < n; ++i){
			ans = min(ans,f[i] + n - i + 1);
		}
		cout << ans << '\n';
	}
} 

1011. 取模

有一点小思路可以快速排除一些数,首先最后取模操作做完之后所有的数的和是 \(\frac n c\) 的倍数

然后我们可以用暴力的手段,在 \(O(m\ln m)\) 的时间内快速求出对于所有 \(k\),操作完之后的所有数的和

介于题目中的 \(c\) 给得很小,所以说按概率这样操作,差不多只会剩下 \(c\) 个数

又因为题目给的 \(c\) 比较小,所以我们只需要对于这 \(c\) 个数,每一个都做一个 \(O(n)\) 的暴力判断即可

最后的时间复杂度 \(O(c\sum n)\)

code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NN = 5e5 + 8;
int T;
int n,m,c;
int a[NN];
int pre[NN];
ll sum;

vector<int> ans;//答案 

int cnt[NN];
void check(int x){
	bool vis = 1;
	int tot = 0;
	for(int i = 1; i <= n; ++i){
		int num = a[i] % x;
		if(cnt[num] == 0) ++tot;
		++cnt[num];
		if(tot > c || cnt[num] > n/c){
			vis = 0;break;
		}
	}
	if(vis) ans.push_back(x);
	for(int i = 1; i <= n; ++i)
		cnt[a[i]%x] = 0; 
}

int main(){
	ios::sync_with_stdio(false),cin.tie(0);
	cin >> T;
	while(T--){
		cin >> n >> m >> c;
		ans.clear();
		sum = 0;
		
		for(int i = 0; i <= m; ++i) pre[i] = 0;
		for(int i = 1; i <= n; ++i){
			cin >> a[i];
			++pre[a[i]];
			sum += a[i]; 
		}
		
		sort(a+1,a+1+n);
		bool vis = 1;
		for(int i = 1; i <= n; i += n/c)//特判-1 
			if(a[i] != a[i + n/c - 1] || (i != 1 && a[i] == a[i-1])){
				vis = 0;break;
			}
		if(vis){
			cout << "-1\n";continue;
		}
		
		for(int i = 1; i <= m; ++i)//预处理每个数的出现次数 
			pre[i] += pre[i-1];
		
		ll del = 0;
		if(c == 1) ans.push_back(1);
		for(int i = 2; i <= m; ++i){
			del = 0;
			for(int j = 1; i * j <= m; ++j){
				del += 1ll * j * (pre[min(m,i*(j+1)-1)]- pre[i*j-1]);
				
			}
			if((sum - del * i) % (n / c) == 0) check(i);
		}
		cout << ans.size() << ' ';
		for(int i = 0; i < ans.size(); ++i){
			cout << ans[i] << ' ';
		}
		cout << '\n';
	}
}

1012. cats 的加减乘除

首先,对于一个算式如果有加减号的话,把加号变减号,减号变加号,那么一定会有另外一个算式,他们两个计算的结果相加,在第一个加减号后面的部分会抵消

比如:

\(1+2\times 3\times 3+5\)\(1-2\times 3\times 4-5\)

最后就只用考虑最前面的1,这样就好做了

那么我们的问题就转化为,求在第一个加减号之前的乘积的期望值

没有 \(-1\) 显然是简单的,答案就是:\(a_1\prod_{i=2}^n (a_i+\frac 1 {a_i})\)

那么有 \(-1\) 怎么做呢?

我们的问题就转化为了求在给定的数中选 \(k\) 个数的乘积的期望

显然这可以用生成函数做,答案就是 \((\prod_{i = 1}^{n} 1+(b_i+\frac 1 {b_i})x)[x^k]\),至于实现?分治 \(NTT\) 即可

但是,我们发现 \(-1\) 如果出现在第一个位置,那么默认是做乘法的,那该怎么做?

我们可以枚举第一个数是哪一个,那么我们的答案就变成了 \(\sum_{i = 1}^n \frac{b_i x}{1+(b_i+\frac 1 {b_i})x}\prod_{i = 1}^{n} 1+(b_i+\frac 1 {b_i})x[x^k]\)

我们发现 \(\sum_{i = 1}^n \frac{b_i x}{1+(b_i+\frac 1 {b_i})x}\) 通分之后,分母与右侧相同,而分子的求解同样可以用分治 \(NTT\) 求解,这样我们就把这道题做完了

因为完整代码太长,所以我们删去了预处理部分和 多项式乘法(\(NTT\)) 板子

code
poly solve(int l, int r, poly& arr) {
	if(r < l) return {1};
    if(l == r) {
        return {1, (arr[l] + inv(arr[l])) % mod}; // 多项式 1 + a_l x
    }
    int mid = (l + r) / 2;
    poly left_poly = solve(l, mid, arr);
    poly right_poly = solve(mid + 1, r, arr);
    return left_poly * right_poly;
}
poly solve2(int l,int r,poly& arr,poly& ar2){
	if(r < l) return {1};
	if(l == r){
		ar2 = {0,arr[l]};
		return {1, (arr[l] + inv(arr[l])) % mod};
	}
	int mid = (l + r) / 2;
	poly ar2l,ar2r;
    poly left_poly = solve2(l, mid, arr, ar2l);
    poly right_poly = solve2(mid + 1, r, arr, ar2r);
    ar2 = ar2l * right_poly + ar2r * left_poly;
    return left_poly * right_poly;
}
int inv4;
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    pre();
    inv2 = jcinv[2];
    inv4 = jcinv[4] * jc[3] % mod;
    int T;
    cin>>T;
    while(T--)
    {
        int n;
        cin>>n;
        int ans = 0,cnt = 0,now = 0;
		for(int i = 1; i <= n; ++i){
			cin >> p_[i];
			if(p_[i] != -1) vis[p_[i]] = 1;
		}
		poly arr;arr.clear();
		for(int i = 1; i <= n; ++i){
			if(!vis[i]) arr.push_back(i);
		}
		
		poly get;
		if(p_[1] != -1){
			get = solve(0,arr.size()-1,arr);
			for(int i = 0; i <= arr.size(); ++i){
				get[i] = get[i] * invc(arr.size(),i) % mod;
			}
			now = p_[1];
		}
		else{
			poly g = solve2(0,arr.size()-1,arr,get);
			for(int i = 1; i <= arr.size(); ++i){
				get[i] = get[i] * invc(i,1) % mod * invc(arr.size(),i) % mod;
			}
			now = 1;
			++cnt;
		}
		for(int i = 2; i <= n; ++i){
//			cout << now * get[cnt] % mod << endl;
			ans = (ans + now * get[cnt] % mod * inv2 % mod) % mod;
			if(p_[i] == -1) ++cnt;
			else now = now * ((p_[i] + jcinv[p_[i]] * jc[p_[i]-1]) % mod) % mod;
			now = now * inv4 % mod;
		}
//		cout << now * get[cnt] % mod << endl;
		
		for(int i = 1; i <= n; ++i)
			if(p_[i] != -1) vis[p_[i]] = 0;
		cout << (ans + now * get[cnt] % mod + mod * 2ll) % mod << endl;
    }
    return 0;
}
posted @ 2025-08-05 19:45  ricky_lin  阅读(71)  评论(0)    收藏  举报