上海站
E - Strange Integers
题意:给定一个长度为n的数字串,和k,求找出尽量多的数,使得每个数之间的差值大于等于k。
思路:可以先排序数组,然后从第一个开始,找第一个与该数差值大于等于k的数,依次查找即可
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); ll a[200001]; ll n,k; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n>>k; for(int i=0;i<n;i++) { cin>>a[i]; } sort(a,a+n); ll ans=0; ll i=0; while(i<n) { ll x=a[i]; ans++; i=lower_bound(a,a+n,x+k)-a; } cout<<ans<<endl; }
D - Strange Fractions
题意:给定pq,找出ab,使得a/b+b/a=p/q
思路:首先,如果p小于两倍q,可以直接得出答案0 0,然后可以通过式子转换,求得求根公式,通过求根公式求a,然后再求得b,中间需要判断是否有整数解
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); ll a[200001]; ll n,k; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin>>n; for(int i=0;i<n;i++) { ll p,q; cin>>p>>q; ll k=__gcd(p,q); p/=k; q/=k; if(p<2*q) { cout<<"0 0"<<endl; continue; } ll x=(ll)sqrt(p*p-4*q*q); if(x*x!=p*p-4*q*q||x<0) { cout<<"0 0"<<endl; continue; } if(x>0) { ll a=(x+p),b=q*2; ll g=__gcd(a,b); a/=g,b/=g; if(a>b)swap(a,b); cout<<a<<" "<<b<<endl; } else cout<<"0 0"<<endl; } }
G - Edge Groups
题意:给定n个点和n-1个边,求“可以将图分离成三个点两条边的组合”的方案数
思路:对于每个点,其有两种状态,向父亲节点借一条边,或者不向父亲节点借边,通过递归找出其子节点是否向其借边,定义状态dp[i],表示第i个节点的方案数,其可以从其子节点转移,首先其方案为子节点的方案数乘积,其次,乘其子节点不需要借的边的组合数。通过递归得出即可
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout);const ll mod=998244353; vector<ll>d[100001]; ll dp[100001];//ans ll dfs(ll now,ll fa) { ll cnt=0;dp[now]=1; for(int i=0;i<d[now].size();i++) { if(d[now][i]==fa)continue; if(dfs(d[now][i],now)) { cnt++; } dp[now]*=dp[d[now][i]]; dp[now]%=mod; } if(cnt%2==0) { for(int i=cnt;i>=2;i-=2) { dp[now]*=(i-1); dp[now]%=mod; } } else { for(int i=cnt+1;i>=2;i-=2) { dp[now]*=(i-1); dp[now]%=mod; } } return !(cnt&1); } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); ll n, k; cin >> n; for (int i = 0; i < n-1; i++) { ll f,t; cin>>f>>t; d[f].push_back(t); d[t].push_back(f); } dfs(1,0); cout<<dp[1]<<endl; }
K - Circle of Life
题意:给定一个数,求出长度为n的01字符串,要求:该字符串在变换2*n次之内可以复原。变换:字符串内1每次变换会向两边分裂1,1和1触碰到时会抵消边变0
思路:我们可以发现,当n为1和3时是无解的,可以提前处理,二的时候也是可以直接得出解的,那么从4开始,我们可以发现,1001的组合是肯定可以恢复的,所以如果是偶数,直接10 01循环添加即可,如果是奇数,我们可以发现0101也是可以恢复原样的,那么就可以在前面加一个010,然后n-3,后面加上10 01循环。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); int main() { ll n; cin>>n; if(n==1||n==3) { cout<<"Unlucky"<<endl;return 0; } if(n==2) { cout<<"10"<<endl;return 0; } if(n%2==1) { n-=3; cout<<"010"; ll cnt=1; for(int i=1;i<=n;i+=2) { if(cnt&1)cout<<"10"; else cout<<"01"; cnt++; } } else { ll cnt=1; for(int i=1;i<=n;i+=2) { if(cnt&1)cout<<"10"; else cout<<"01"; cnt++; } } }
H - Life is a Game
题意:给定n条边和权值以及点的权值,以及q个询问,每次询问提供点编号x以及初始值,每次经过点可以获得其权值,问从x出发最多可以获得多少值(值大于等于边的权值才能经过这条边)。
思路 :这题本来想着是用堆优化的类kruskal做的,每次将新加的边加入集合,然后往外拓展找新边,但是T了,后来训练赛结束后发现kruskal重构树加离线可以解,kruskal重构树可以参考严格鸽的文章
https://zhuanlan.zhihu.com/p/567569277
大概意思就是先跑一遍kruskal,在跑的过程中,边建一个节点连接两个点,然后每次新加边都增加节点,重构树的叶子节点就是所有的点,然后网上都是边节点,且边权值自下而上逐级递增,那么就可以方便找到第一个不满足当前权值加初始权值大于等于边权值的边了,然后利用离线优化,从初始权值小的开始找,这样就能保证每次找到的级数高于前面的,方便记忆化搜索。
#include<bits/stdc++.h> using namespace std; #define ull unsigned long long #define ll long long #define endl "\n" #define debug freopen("C:/Users/HBH/Desktop/1.txt","w",stdout); struct edge {
ll id; ll u; ll v; ll w; }; struct que { ll u; ll k; ll ans; }; que ques[200001]; ll n, m, q; ll idx; ll val[200001]; vector<ll>tri[200001]; ll F[200001]; ll V[200001]; ll sum[200001]; ll up[200001]; ll tag[200001]; vector<edge>e; bool cmp(edge a, edge b) { return a.w < b.w; } bool cmpque(que a, que b) { return a.k < b.k; } bool cmpid(que a, que b) { return a.id < b.id; } ll find(ll x)//并查集辅助kruskal { if (x == F[x])return x; else return F[x]=find(F[x]); } void init(ll u, ll v, ll w) { ll a = find(u); ll b = find(v);//找到当前节点的最上级父亲 if (a == b)return ; F[a] = idx; F[b] = idx;//连接两个集合到一个新的边点 F[idx] = idx; V[idx] = w;//记录边权值 tri[idx].push_back(a);//记录边的孩子节点,待会需要建立重构树 tri[idx].push_back(b); idx++; } ll dfs(ll pos) { if (pos <= n) { sum[pos] = val[pos]; return sum[pos];//记录每个点的点权值和 } ll s = 0; for (int i = 0; i < tri[pos].size(); i++) { F[tri[pos][i]] = pos; s += dfs(tri[pos][i]); } sum[pos] = s; return s; } ll check(ll u, ll k) { //cout << k << " " << u << endl; if (up[u])u = up[u];//快速跳到当前节点能跳到的最高节点 if(u==idx-1){up[u]=u;return u;}//到达根节点了,不能再往上跳了 if (k + sum[u] >= V[F[u]])//满足条件 { up[u] = check(F[u], k); } else {up[u] = u;}//不满足条件,直接截止 return up[u]; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m >> q; for (int i = 1; i <= n; i++) { cin >> val[i]; } idx = n + 1; for (int i = 1; i <= n; i++) { F[i] = i; } for (int i = 0; i < m; i++) { ll u, v, w; cin >> u >> v >> w; e.push_back({u, v, w}); } sort(e.begin(), e.end(), cmp); for (int i = 0; i < m; i++) { init(e[i].u, e[i].v, e[i].w); } ll root = idx - 1; dfs(root); for (int i = 0; i < q; i++) { ll u, v; cin >> u >> v; ques[i] = {i u, v}; } sort(ques, ques + q, cmpque);//按询问的初始值从小到大排 for (ll i = 0; i < q; i++) { ques[i].ans=sum[check(ques[i].u, ques[i].k)]+ques[i].k;//直接记录答案,防止同一个点查询两次干扰答案 } sort(ques, ques + q, cmpid);//按原序排回 for (int i = 0; i < q; i++) { cout << ques[i].ans << endl; } }