E. GCD VS XOR
1.1翻译
有一个正整数 x。他想让你找出另一个严格小于 x的正整数 y,使等式 gcd(x,y)=x^y成立。
1.2思路
取x - lowbit(x) ,如果 x 是 2 的整数次幂则无解(打表)。
考虑二进制形式下的倍数关系,唯一比较方便的就是乘2操作(二进制下末尾加0)。
例如 x = 1010101000,一定有因子8(末尾有三个零),若保证最大公约数为8,y的末尾至少有3个0,又要保证x^y = 8,所以取y = x - lowbit(x);
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
map<int,int>heap;
int lowbit(int x){
return x & -x ;
}
void solve()
{
int n;
cin >> n;
if(heap[n]==1)
{
cout<<-1<<endl;
return;
}
cout << n - lowbit(n) << endl;
}
signed main()
{
int x = 1;
heap[x]++;
for(int i = 1;i<=59;i++)
{
x*=2;
heap[x]++;
}
int t;
cin >> t;
//t = 1;
while(t--)
{
solve();
}
}
C. Red Walking on Grid
2.1翻译
在2⋅n网格上,有些单元格是红色的,有些是白色的。
最初可以选择一个红色单元格,每一步都可以选择上面、下面、左边或右边的一个红色单元格。当离开一个单元格时,该单元格会立即变成白色。
想知道她最多可以走多少步。
2.2思路
显然从左往右为最优,考虑动态规划,每个点走的最多的步数可以由左边格子转移过来。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+10;
char arr[3][N];
int dp[3][N];
void solve()
{
int n;
cin >> n;
for(int i = 1; i <= 2; i++)
{
for(int j = 1; j <= n; j++)
{
cin >> arr[i][j];
}
}
for(int i = 1; i <= n; i++)
{
if(arr[1][i]=='R')
{
dp[1][i] = dp[1][i-1] + 1;
}
if(arr[2][i]=='R')
{
dp[2][i] = dp[2][i-1] + 1;
}
if(arr[1][i]=='R'&&arr[2][i]=='R')
{
int u = dp[1][i], k = dp[2][i];
dp[1][i] = max(dp[1][i], k + 1);
dp[2][i] = max(dp[2][i], u + 1);
}
}
int ans = 0;
for(int i = 1;i<=n;i++)
{
ans = max(ans,dp[1][i]);
ans = max(ans,dp[2][i]);
}
cout<<max(0,ans-1)<<endl;
}
int main()
{
int t;
//cin >> t;
t = 1;
while(t --)
{
solve();
}
}
H. Instructions Substring
3.1翻译
小红站在直角坐标系的坐标 (0,0)处。她有一串指令:上、下、左、右(其中 "右 "使 x 坐标增加1,"上 "使 y 坐标增加1)。
现在,小红想要选择连续的指令子串并执行它们。小红希望最终执行的指令能通过坐标 (x,y)。她想知道有多少个选择项。
3.2思路
对于每一个L(区间的左端点),都找到对应的R(区间右端点),使得经过操作[L,R]后,正好处在(x,y)上,随后的操作都已经经过了(x,y),预处理前缀和,放进一个multimap里,找到大于当前位置满足条件的即可
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
typedef pair<int,int> PII;
const int N = 2e6+10;
int tmps[N];
int tmpl[N];
multimap<PII,int>heap;
void solve()
{
int n;
cin >> n;
int x,y;
cin >> x >> y;
string s;
cin >> s;
s = ' ' + s;
for(int i = 1;i <= n;i ++)
{
if(s[i]=='W')
{
tmps[i]++;
}
else if(s[i]=='S')
{
tmps[i]--;
}
else if(s[i]=='A')
{
tmpl[i]--;
}
else
{
tmpl[i]++;
}
}
if(x==0&&y==0)
{
int at = 0;
for(int i = 1;i<=n;i++)
{
at += n-i+1;
}
cout<<at<<endl;
return;
}
for(int i = 1;i <= n;i ++)
{
tmps[i] += tmps[i-1];
tmpl[i] += tmpl[i-1];
heap.insert({{tmps[i],tmpl[i]},i});
}
int ans = 0;
for(int i = 1;i <= n;i ++)
{
int x1 = x + tmpl[i-1];
int y1 = y + tmps[i-1];
for(auto pos = heap.equal_range({y1,x1});pos.first != pos.second;++pos.first)
{
if(pos.first->second>=i)
{
ans+=n-pos.first->second+1;
break;
}
}
}
cout<< ans << endl;
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t;
//cin >> t;
t = 1;
while(t --)
{
solve();
}
}
C. MST
4.1翻译
求子图的最小生成树
4.2.1思路
对于顶点数量大于根号n的情况,枚举所有边求最小生成树
对于顶点数量小于根号n的情况,n^2枚举所有顶点,找出同时包括两个顶点的边,利用这些边求最小生成树
至于为什么用根号n来分治:
求最小生成树的时间复杂度是O(eloge),题目保证所有点数之和小于1e5,假设顶点数量为base,
其中大于base的数据最多有1e5/base组,其时间复杂度为O(m*(1e5/base))(提前排序,且并查集写为路径压缩和按秩合并)
同理小于base的数据有最多有1e5/base组,其时间复杂度为O((1e5/base)*base*base);
两种情况分别小于1e8
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int> PII;
const int N = 1e5+10;
int pre[N];
int rank1[N];
map<PII,int>edgex;
//代表第i个元素需要初始化
void creat(int i)
{
pre[i]=i;
rank1[i]=0;
}
//路径压缩(非递归)
int find(int x)
{
int k, j, r;
r = x;
while(r != pre[r])
r = pre[r];
k = x;
while(k != r)
{
j = pre[k];
pre[k] = r;
k = j;
}
return r;
}
//按秩合并
bool Union(int i,int j)
{
i=find(i);
j=find(j);
if(i==j) return true;
if(rank1[i]>rank1[j]) pre[j]=i;
else
{
if(rank1[i]==rank1[j]) rank1[j]++;
pre[i]=j;
}
return false;
}
struct Edge
{
int a,b,w;
bool operator < (const Edge&W) const
{
return w<W.w;
}
}edge[N*2];
void solve()
{
int n,m,q;
cin >> n >> m >> q;
for(int i = 1; i <= m; i++)
{
int a,b,c;
cin >> a >> b >> c;
edge[i].a = a ;
edge[i].b = b ;
edge[i].w = c;
edgex[{a,b}] = c;
}
sort(edge+1,edge+m+1);
while(q--)
{
int k;
cin >> k;
if(k<320)
{
int res = 0;
vector<int>no;
for(int i = 1; i <= k; i++)
{
int x;
cin >> x;
creat(x);
no.push_back(x);
}
std::vector<Edge> vx;
if(no.size()==1)
{
cout << 0 <<endl;
continue;
}
for(int i = 0; i < no.size(); i++)
{
for(int j = 0 ; j < no.size() ; j++)
{
if(edgex.find({no[i],no[j]}) != edgex.end())
{
vx.push_back({no[i],no[j],edgex[{no[i],no[j]}]});
}
}
}
sort(vx.begin(),vx.end());
int cnt = 0;
int xx = vx.size() - 1;
for(int i = 0 ; i <= xx; i++)
{
int a = vx[i].a;
int b = vx[i].b;
int w = vx[i].w;
if(!Union(a,b))
{
cnt ++;
res+=w;
}
}
if(cnt < k - 1) cout << -1 << "\n";
else cout << res <<endl;
}
else
{
int res = 0;
vector<bool> tip(n + 10);
for(int i = 1; i <= k ;i++)
{
int x;
cin >> x;
creat(x);
tip[x] = 1;
}
int cnt = 0;
for(int i = 1; i <= m; i++)
{
if(tip[edge[i].a]&&tip[edge[i].b])
{
int a = edge[i].a;
int b = edge[i].b;
int w = edge[i].w;
if(!Union(a,b))
{
cnt ++;
res+=w;
}
}
}
if(cnt < k - 1) cout << -1 << "\n";
else cout << res <<endl;
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int t;
//cin >> t;
t = 1;
while(t --)
{
solve();
}
}
4.2.2思路
由于存在类菊花图的可能,所以考虑如何降低,边数多的点所存的边。
三元环计数的方式,将边存成单向边,主要思路是不多枚举
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
using namespace std;
typedef pair<int,int> PII;
typedef long long LL;
const int P = 998244353;
const int N = 1e5 + 10;
int e[N * 2],ne[N * 2],h[N],idx,w[N * 2];
struct Edge
{
int a,b,w;
bool operator < (const Edge&W) const
{
return w<W.w;
}
}edge[N * 2];
int d[N],vis[N];
int pre[N];
int rank1[N];
map<PII,int>edgex;
void creat(int i)
{
pre[i]=i;
rank1[i]=0;
}
//路径压缩(非递归)
int find(int x)
{
int k, j, r;
r = x;
while(r != pre[r])
r = pre[r];
k = x;
while(k != r)
{
j = pre[k];
pre[k] = r;
k = j;
}
return r;
}
//按秩合并
bool Union(int i,int j)
{
i=find(i);
j=find(j);
if(i==j) return true;
if(rank1[i]>rank1[j]) pre[j]=i;
else
{
if(rank1[i]==rank1[j]) rank1[j]++;
pre[i]=j;
}
return false;
}
void add(int a,int b,int c)
{
e[idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx ++;
}
void solve()
{
memset(h, -1 ,sizeof h);
int n,m,q;
cin >> n >> m >> q;
for(int i = 1; i <= m; i ++)
{
int a,b,c;
cin >> a >> b >> c;
edgex[{a,b}] = c;
edgex[{b,a}] = c;
d[a] ++;
d[b] ++;
edge[i] = {a,b,c};
}
for(int i = 1; i <= m; i ++)
{
int x = edge[i].a;
int y = edge[i].b;
int w = edge[i].w;
if(d[x] < d[y]) add(x,y,w);
else if(d[x] == d[y] && x < y) add(x,y,w);
else add(y,x,w);
}
while(q--)
{
int x;
cin >> x;
set<int>q;
std::vector<int>vm(x + 10);
for(int i = 1; i <= x; i ++)
{
int llx;
cin >> llx;
vm[i] = llx;
creat(llx);
q.insert(llx);
}
vector<Edge>vx;
for(int i = 1; i <= x; i ++)
{
int jx = vm[i];
for(int j = h[jx]; j != -1; j = ne[j])
{
int u = e[j];
if(q.find(u) != q.end())
{
vx.push_back({jx,u,edgex[{jx,u}]});
}
}
}
sort(vx.begin(),vx.end());
int cnt = 0;
int xx = vx.size() - 1;
int res = 0;
for(int i = 0 ; i <= xx; i++)
{
int a = vx[i].a;
int b = vx[i].b;
int w = vx[i].w;
if(!Union(a,b))
{
cnt ++;
res+=w;
}
}
if(cnt < x - 1) cout << -1 << "\n";
else cout << res <<endl;
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
int tt;
//cin >> tt;
tt = 1;
while(tt --)
{
solve();
}
}