2021 icpc上海(E,D,G,I,H)

E. Strange Integers

题意:

给定 n 个整数 A1,A2,⋯,An 和一个参数 k ,你应该选择一些整数 Ab1,Ab2,⋯,Abm(1≤b1<b2<⋯<bm≤n) 以便 ∀1≤i<j≤m,|Abi−Abj|≥k。求你能选择的最大整数个数。

由于选择的任何两个数差值都要大于等于k,所以值相同的数也只能选一个
贪心,升序,从最小的开始,能选就选,为后面的选择留有更多的选择空间
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=1e5+10;
int a[N];
int n,k;
void solve() {
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	int now=a[1];
	int ans=1;
	for(int i=2;i<=n;i++){
		if(a[i]-now>=k) {
			now=a[i];
			ans++;
		}
	}
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

D. Strange Fractions

题意:

给定一个正分数 p/q ,你应该找出 a,b 和 p/q=a/b+b/a 这两个正整数。如果没有这样的整数,请报告。

求什么,设什么, a b \frac{a}{b} ba b a \frac{b}{a} ab 互为倒数,将 a b \frac{a}{b} ba 看作一个整体,设为x

p q = x + 1 x = = > \frac{p}{q}=\frac{x+1}{x}==> qp=xx+1==> q x 2 qx^2 qx2 − p x -px px + q = 0 +q=0 +q=0
对于一元二次方程 a x 2 + b x + c = 0 ax^2+bx+c=0 ax2+bx+c=0
求根公式:
x 1 x_1 x1=$\frac{-b+\sqrt{b^2-4ac}}{2a
} $
x 2 x_2 x2= − b − b 2 − 4 a c 2 a \frac{-b-\sqrt{b^2-4ac}}{2a } 2abb24ac

韦达定理:
x 1 x_1 x1+ x 2 x_2 x2= − b a \frac{-b}{a} ab , x 1 x_1 x1* x 2 x_2 x2=$\frac{c}{a} $
根据求根公式,$\frac{a}{b} = = =x_1 = = =\frac{p+\sqrt{p2-4q2}}{2q}$

然后最终在是整数的情况下, a = p + p 2 − 4 q 2 a=p+\sqrt{p^2-4q^2} a=p+p24q2 b = 2 q b=2q b=2q

注意,尽量整数运算,小数运算有精度误差

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
int p,q;
void solve() {
	cin>>p>>q;
	if(p*p<4*q*q){
		cout<<0<<' '<<0<<endl;
		return;
	}
	int x=(int)sqrt(p*p-4*q*q);
	if(x*x!=p*p-4*q*q){
		cout<<0<<' '<<0<<endl;
		return;
	}
	int a=p+x,b=2*q;
	cout<<a<<' '<<b<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

G. Edge Groups

题意:

给定一个由 n 个顶点和 n−1 条边组成的无向连通图,其中 n 保证为奇数。您希望在以下约束条件下将所有 n−1 条边划分为 (n−1)/2 组:

  • 每个组中正好有 2 条边
  • 同一组中的 2 条边有一个共同顶点

确定以 998244353 为模数的有效分割方案的数目。如果有 2 条边在一个方案中属于同一组,而在另一个方案中不属于同一组,那么这两个方案就被认为是不同的。

树形dp
从下往上,假设某个节点u有x个子节点,如果x为偶数,那么必须它们内部两两匹配;如果x为奇数,那么必须选一条和u往上的那条边匹配,剩下的两两匹配

那么假设x个两两匹配一共有几种方案呢,就是首先在x中选2个,再在x-2中选两个…直到选不了,重复了cnt的阶乘次(cnt为组数),除以重复次数即可

假设x为7,那么方案数为$ \frac{C_{7}{2}C_{5}{2}C_{3}^{2}}{3!} =\frac{76}{2} \frac{54}{2} \frac{3*2}{2}\frac{1}{3!} =\frac{7!}{2^33!} $

假设x为8,那么方案数为$\frac{C_{8}{2}C_{6}{2}C_{4}{2}C_{2}{2}}{4!} =\frac{87}{2}\frac{65}{2}\frac{43}{2}\frac{21}{2}\frac{1}{4!}=\frac{8!}{2^44!} $

故方案数为$\frac{x!}{2^{\frac{x}{2}}{\frac{x}{2}}!} $

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 3e5 + 2, mod = 998244353;
int n, m, q, k;
int a[N];
int fac[N];
int pre[N];
int vis[N];
int ans;
vector<vector<int>> e(N);
int qmi(int a,int b){
	int res=1;
	while(b){
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
void dfs(int u, int fa)
{
	if (u != 1 && e[u].size() == 1){
		return;
	}
	int cnt = 0;
	for (auto v : e[u])
	{
		if (v == fa) continue;
		dfs(v, u);
		if (vis[v] == 0) cnt++;
	}
	if(cnt%2) vis[u]=1;
	ans=ans*fac[cnt]%mod*qmi(pre[cnt/2],mod-2)%mod*qmi(fac[cnt/2],mod-2)%mod;
}
void solve()
{
	cin >> n;
	for (int i = 1; i <= n - 1; i++)
	{
		int u, v;
		cin >> u >> v;
		e[u].push_back(v);
		e[v].push_back(u);
	}
	ans = 1;
	dfs(1, -1);
	cout << ans << endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
	fac[0]=1;
	for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%mod;
	pre[0]=1;
	for(int i=1;i<N;i++) pre[i]=pre[i-1]*2%mod;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

I. Steadily Growing Steam

题意:

n 个卡牌,有价值 vi 和点数 ti ,你需要从中选出两组卡牌使得其点数和相等,然后最大化这两组的价值和。另外你可以选择不超过 k 个不同的卡牌,使得其 ti 变成 2ti。

0≤k≤n≤100,ti≤13,|vi|≤ 1 0 9 10^9 109

有限制的选择-->背包
不是经典背包,视为背包变形
因为要选出两组卡牌,也就是说装入两个背包,但是两个写不了,所以我们就转化为两组点数和之差,把这个当作背包的体积,如果第i张卡牌放入第一组,那么体积增加ti,如果放入第二组,那么体积减少ti,最后只需要取体积为0的时候的答案即可
还有别的信息,就是点数翻倍的次数,所以我们需要再加一维,剩余翻倍次数 
背包体积范围是[-2600,2600],因为下标不能为负,所以加一个2600的偏移量
#include<bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
const int N=110,M=5210,base=2600;
int v[N],t[N];
int dp[N][N][M];// dp[i][j][w]表示只考虑前i个物品、还可翻倍j次、当前背包体积为w的价值之和最大值
int n,k;
void solve() {
	cin>>n>>k;
	for(int i=1;i<=n;i++) cin>>v[i]>>t[i];
	for(int i=0;i<=n;i++){
		for(int j=0;j<=k;j++){
			for(int w=0;w<=5200;w++){
				dp[i][j][w]=-1e18;
			}
		}
	}
	dp[0][0][0+base]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=k;j++){
			for(int w=-2600+base;w<=2600+base;w++){
				//不选t[i]
				dp[i][j][w]=dp[i-1][j][w];
				//t[i]放入第一组
				if(w-t[i]>=0) dp[i][j][w]=max(dp[i][j][w],dp[i-1][j][w-t[i]]+v[i]);//前面的if判断是为了防止下标越界,,或者说是超出状态表示的范围了,不能转移过来
				//t[i]放入第二组
				if(w+t[i]<=5200) dp[i][j][w]=max(dp[i][j][w],dp[i-1][j][w+t[i]]+v[i]);
				//t[i]*2放入第一组
				if(j-1>=0&&w-2*t[i]>=0) dp[i][j][w]=max(dp[i][j][w],dp[i-1][j-1][w-2*t[i]]+v[i]);
				//t[i]*2放入第二组
				if(j-1>=0&&w+2*t[i]<=5200) dp[i][j][w]=max(dp[i][j][w],dp[i-1][j-1][w+2*t[i]]+v[i]);
			}
		}
	}
	int ans=-1e18;
	for(int j=0;j<=k;j++) ans=max(ans,dp[n][j][2600]);
	cout<<ans<<endl;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	int t=1;
//    cin>>t;
	while(t--) {
		solve();
	}
	return 0;
}

H. Life is a Game

参考2021 上海 ICPC 区域赛 - kpole - 博客园

题意:

n 个点 m 条边,有点权和边权,q 个询问,每个询问 x,k 表示从 x 出发,带着 k 的能力,通过一条边的条件是能力大于这条边的权值,当第一次到达一个点时可以将该点的权值累加到能力上,求可以获得的最大能力。

考虑离线询问,每个点带有若干个询问,每个询问包含初试能力值和询问ID。然后从小到大枚举每条边(x,y,w),设 v[x]是 x 的点权,那么对于从 x 出发的询问,如果能力值 k+v[x]<w,那么说明它无法到达除去 x 之外的所有点,它的答案就是 k+v[x],紧接着把该询问从 x 的询问集合中删除。之后对于 y 做同样的处理。

此时,x 和 y 中询问都可以通过 (x,y,w)这条边,所以,可以将 x 与 y 合并成一个集合,然后该集合的点权和就是 v[x]+v[y]。这里可以用一个带权并查集合并。另外询问也需要合并,按照启发式合并的思想,每次小的集合向大的集合合并即可。

总体复杂度为 O( m l o g m + q l o g n l o g q mlogm+qlognlogq mlogm+qlognlogq)) 。

其中边的排序 m l o g m mlogm mlogm

q次询问,每次询问最多会被处理 l o g n logn logn次,multiset的复杂度为 l o g q logq logq

关于启发式合并的思想简单提一下,每次小的集合向大的集合合并,那么对于小的集合而言,大小扩大二倍以上,所以每个点从小的集合删除再加入大的集合的次数不会超过 l o g n logn logn

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
int n, m, q;
struct Edge
{
    int x, y, w;
    bool operator<(const Edge &b) const
    {
        return w < b.w;
    }
} e[N];
int f[N], v[N], rs[N];
multiset<pair<int, int>> st[N];
int get(int x) { return x == f[x] ? f[x] : f[x] = get(f[x]); }
int main()
{
    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++)
        scanf("%d", &v[i]), f[i] = i;
    for (int i = 1; i <= m; i++)
    {
        int x, y;
        scanf("%d%d%d", &e[i].x, &e[i].y, &e[i].w);
    }
    sort(e + 1, e + 1 + m);
    for (int i = 1; i <= q; i++)
    {
        int x, k;
        scanf("%d%d", &x, &k);
        st[x].insert({k, i});
    }
    int p = 1;
    for (int i = 1; i <= m; i++)
    {
        int x = e[i].x, y = e[i].y, w = e[i].w;
        x = get(x);
        y = get(y);
        if (x == y)
            continue;
      	// 从 x 出发的集合无法跨过 w
        while (st[x].size() && (*st[x].begin()).first < w - v[x])
        {
            int id = st[x].begin()->second;
            rs[id] = v[x] + st[x].begin()->first;
            st[x].erase(st[x].begin());
        }
        while (st[y].size() && (*st[y].begin()).first < w - v[y])
        {
            int id = st[y].begin()->second;
            rs[id] = v[y] + st[y].begin()->first;
            st[y].erase(st[y].begin());
        }
        // 启发式合并,从小的集合合并到大的集合中
        if (st[x].size() > st[y].size())
            swap(x, y);
        while (st[x].size())
        {
            st[y].insert(*st[x].begin());
            st[x].erase(st[x].begin());
        }
        f[x] = y;
        v[y] += v[x];
    }
	// 还有一些询问留在最后处理
    for (int i = 1; i <= n; i++)
    {
        int x = get(i);
        for (auto &t : st[x])
        {
            rs[t.second] = v[x] + t.first;
        }
        st[x].clear();
    }
    for (int i = 1; i <= q; i++)
    {
        printf("%d\n", rs[i]);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值