文章目录
- [E. Strange Integers](https://codeforces.com/gym/103446/problem/E)
- [D. Strange Fractions](https://codeforces.com/gym/103446/problem/D)
- [G. Edge Groups](https://codeforces.com/gym/103446/problem/G)
- [I. Steadily Growing Steam](https://codeforces.com/gym/103446/problem/I)
- [H. Life is a Game](https://codeforces.com/gym/103446/problem/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 }
2a−b−b2−4ac
韦达定理:
x
1
x_1
x1+
x
2
x_2
x2=
−
b
a
\frac{-b}{a}
a−b ,
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+p2−4q2, 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]);
}
}