前言
D 和 E 阴到没边了......
一、A - illegal
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
string s;
cin>>s;
int n=s.length();
if(n%5)
{
No;
}
Yes;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
没啥好说的,判断直接输出即可。
二、B - Personnel Change
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n,m;
cin>>n>>m;
vector<int>cnts(m+1);
for(int i=1,a,b;i<=n;i++)
{
cin>>a>>b;
cnts[b]++;
cnts[a]--;
}
for(int i=1;i<=m;i++)
{
cout<<cnts[i]<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
没啥好说的,因为原本位置每有一个数,就会对原来答案产生 -1 的贡献。而去往位置每有一个数,就会对答案产生 +1 的贡献,所以每次统计一下贡献输出即可。
三、C - Understory
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int q;
cin>>q;
map<int,int>cnts;
int sum=0;
int op,h;
while(q--)
{
cin>>op>>h;
if(op==1)
{
cnts[h]++;
sum++;
}
else
{
vector<int>del;
for(auto [k,v]:cnts)
{
if(k>h)
{
break;
}
sum-=cnts[k];
del.push_back(k);
}
for(auto k:del)
{
cnts.erase(k);
}
}
cout<<sum<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
因为每个数会被添加一次,被删除一次,所以就可以每次暴力删除,复杂度是 O(n)。那么考虑用一个 map 来维护,每次暴力删除即可。
四、D - Concat Power of 2
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
void solve()
{
int n;
cin>>n;
const int MAXN=30;
vector<ll>pow(MAXN);
vector<ll>len(MAXN);
for(int i=0;i<MAXN;i++)
{
pow[i]=1<<i;
len[i]=to_string(pow[i]).length();
}
vector<ll>bit(10);
bit[0]=1;
for(int i=1;i<10;i++)
{
bit[i]=10*bit[i-1];
}
vector<set<ll>>nums(10);
nums[0].insert(0);
for(int i=1;i<=9;i++)
{
for(int j=0;j<MAXN;j++)
{
if(len[j]>i)
{
break;
}
for(auto x:nums[i-len[j]])
{
nums[i].insert(x*bit[len[j]]+pow[j]);
}
}
}
set<ll>res;
for(int i=1;i<=9;i++)
{
for(auto x:nums[i])
{
res.insert(x);
}
}
for(auto x:res)
{
if(--n==0)
{
cout<<x<<endl;
return ;
}
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
沟槽的 Attention is all you need......
观察样例三,注意到当好数逼近 10^9 时,此时 n 的规模是 10^6。那么其实是可以暴力搜,反正最多不超过 10^6 个。
考虑根据长度暴力搜,那么就是一个类似 dp 的过程。对于当前长度 i,枚举每个 2 的幂。若当前幂的长度为 j,那么就可以拼在所有长度为 i-j 的数转移过来。
五、E - Tree Distance
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
//自定义
DSU(int n){
father.assign(n,0);
for(int i=0;i<n;i++){
father[i]=i;
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int x,int y){
int fx=find(x);
int fy=find(y);
if(fx==fy){
return false;
}
father[fx]=fy;
return true;
}
};
void solve()
{
int n;
cin>>n;
vector<vector<ll>>a(n+1,vector<ll>(n+1));
vector<array<ll,3>>edge;
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
cin>>a[i][j];
edge.push_back({i,j,a[i][j]});
}
}
sort(edge.begin(),edge.end(),[&](auto &x,auto &y)
{
return x[2]<y[2];
});
DSU dsu(n+1);
vector<vector<pll>>g(n+1);
for(auto [u,v,w]:edge)
{
if(!dsu.same(u,v))
{
dsu.merge(u,v);
g[u].push_back({v,w});
g[v].push_back({u,w});
}
}
vector<vector<ll>>ans(n+1,vector<ll>(n+1));
for(int i=1;i<=n;i++)
{
auto dfs=[&](auto &&self,int u,int fa,ll dis)->void
{
ans[i][u]=dis;
for(auto [v,w]:g[u])
{
if(v!=fa)
{
self(self,v,u,dis+w);
}
}
};
dfs(dfs,i,0,0);
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(ans[i][j]!=a[i][j])
{
No;
}
}
}
Yes;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
纯你妈 guess 啊......
注意到在暴力按给定关系连边后,若树存在,那么其一定是这张图的最小生成树。
考虑证明这个结论。
运用反证法,假设该合法的树不是最小生成树。那么在 Kruskal 构建最小生成树的过程中,对于当前要选的边 (u,v),其必然是连接 u 和 v 两个连通块权值最小的边。那么如果不选这条边而是选择 (x,y) 这条边来连接这两个连通块,则必有权值 ,也就是有
。那么因为此时选择了 (x,y) 这条边,且边权非负,所以就导致从 u 到 v 的路径必然大于从 x 到 y 的路径,也就是有
。这是矛盾的,所以可以说明合法的树必然是最小生成树。
六、F - Make Bipartite 3
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
struct DSU{
vector<int>father;
//自定义
vector<int>sz;
vector<int>color;
vector<int>cnts;
vector<vector<int>>child;
DSU(int n){
father.assign(n,0);
sz.assign(n,1);
color.assign(n,0);
cnts.assign(n,0);
child.assign(n,vector<int>(0));
for(int i=0;i<n;i++){
father[i]=i;
child[i].push_back(i);
}
}
int find(int i){
if(i!=father[i]){
father[i]=find(father[i]);
}
return father[i];
}
bool same(int x,int y){
return find(x)==find(y);
}
bool merge(int fx,int fy){
father[fx]=fy;
sz[fy]+=sz[fx];
for(auto u:child[fx])
{
color[u]^=1;
if(color[u])
{
cnts[fy]++;
}
child[fy].push_back(u);
}
return true;
}
};
void solve()
{
int n,q;
cin>>n>>q;
int ans=0;
DSU dsu(n+1);
int u,v;
while(q--)
{
cin>>u>>v;
if(ans==-1)
{
cout<<-1<<endl;
continue;
}
if(dsu.same(u,v))
{
if(dsu.color[u]==dsu.color[v])
{
ans=-1;
}
}
else
{
int fu=dsu.find(u);
int fv=dsu.find(v);
ans-=min(dsu.cnts[fu],dsu.sz[fu]-dsu.cnts[fu]);
ans-=min(dsu.cnts[fv],dsu.sz[fv]-dsu.cnts[fv]);
int chg=(dsu.color[u]==dsu.color[v]);
if(dsu.sz[fu]>dsu.sz[fv])
{
swap(fu,fv);
}
for(auto x:dsu.child[fu])
{
dsu.color[x]^=chg;
if(dsu.color[x])
{
dsu.cnts[fv]++;
}
dsu.child[fv].push_back(x);
}
dsu.father[fu]=fv;
dsu.sz[fv]+=dsu.sz[fu];
ans+=min(dsu.cnts[fv],dsu.sz[fv]-dsu.cnts[fv]);
}
cout<<ans<<endl;
}
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
//cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
这就是启发式合并嘛,感觉以前是见过的。
首先不难想到,因为每次只能加边,所以若当前图不是个二分图了,之后不管怎么操作,这张图就都不可能是二分图了。
首先可以发现,对于同一个连通块内的点,其染色情况是可以任意翻转的。那么由于要求黑色的个数最少,所以就是连通块内黑色和白色的点数取最小。所以若黑色点的个数是 cnt,连通块的大小是 size,那么答案就是 min(cnt,size-cnt)。所以可以发现,不管如何翻转,最终答案都是不变的。
所以,考虑用并查集维护出当前所有连通块内的染色情况,然后对当前要连的边 (u,v) 进行讨论。
第一种情况,若原本 u 和 v 在同一个连通块内,那么若 u 和 v 同色,则此时就必然不合法了。否则,就什么也不用做即可。
第二种情况,若 u 和 v 不在同一个连通块内,若此时 u 和 v 不同色,那么直接连接即可。若是同色的,那么此时就需要把其中一个连通块内所有点的颜色翻转一下,然后再连接。
对于第二种情况,首先需要把原本这两个连通块的贡献减掉,再加上连接后大连通块的贡献。之后,需要考虑翻转问题。由于直接随意暴力全翻转的话,最差会一直把所有点都翻转一遍,所以复杂度是 O(n^2) 的。
此时就涉及到启发式合并了,即每次暴力翻转较小的连通块内的所有点。观察每个点被翻转的次数,对于当前两个连通块 A 和 B,其中 ,就有
。所以每次合并后,较小的连通块的大小至少翻一倍。所以对于每个点,初始只有自己,大小为 1,每合并一次大小翻一倍,所以其最多就只会被翻转 O(log n) 次,所以是可以的。
七、G - Minimum XOR Walk
cpp
#include <bits/stdc++.h>
using namespace std;
/* /\_/\
* (= ._.)
* / > \>
*/
/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/
#define endl '\n'
#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef long double ld;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};
template<int BIT,typename T=ll>
struct Xor_Basis{
int cnt;
bool zero;
bool normal;
T data[BIT+1];
Xor_Basis(){reset();}
void reset(){
cnt=0;
zero=false;
normal=false;
for(int i=0;i<=BIT;i++)data[i]=0;
}
bool insert(T x){
for(int i=BIT;i>=0&&x;i--){
if(x>>i&1){
if(data[i]==0){
data[i]=x;
cnt++;
normal=false;
return true;
}
x^=data[i];
}
}
zero=true;
return false;
}
void rebuild(){
if(normal){
return;
}
for(int i=BIT;i>=0;i--){
if(data[i]==0){
continue;
}
for(int j=i-1;j>=0;j--){
if(data[i]>>j&1){
data[i]^=data[j];
}
}
}
normal=true;
}
T query_max(T x=0){
for(int i=BIT;i>=0;i--){
if(~(x>>i)&1){
x^=data[i];
}
}
return x;
}
T query_min(T x){
for(int i=BIT;i>=0;i--){
if(x>>i&1){
x^=data[i];
}
}
return x;
}
//kth minimum
T query_kth(ll k){
rebuild();
if(zero){
k--;
}
if(k<0||k>=(1ll<<cnt)){
return -1;
}
T res=0;
for(int i=0;i<=BIT;i++){
if(data[i]){
if(k&1){
res^=data[i];
}
k>>=1;
}
}
return res;
}
ll query_rank(T x){
rebuild();
ll res=0,k=1;
for(int i=0;i<=BIT;i++){
if(data[i]){
if(x>>i&1){
res|=k;
}
k<<=1;
}
}
return res+zero;
}
//线性基合并
friend constexpr Xor_Basis operator|(const Xor_Basis &lhs, const Xor_Basis &rhs){
Xor_Basis res=lhs;
for(int i=0;i<=BIT;i++){
if(rhs.data[i]){
res.insert(rhs.data[i]);
}
}
res.zero=lhs.zero|rhs.zero;
return res;
}
constexpr Xor_Basis& operator|=(const Xor_Basis &rhs){
for(int i=0;i<=BIT;i++){
if(rhs.data[i]){
insert(rhs.data[i]);
}
}
return *this;
}
//线性基求交
friend constexpr Xor_Basis operator&(const Xor_Basis &lhs, const Xor_Basis &rhs){
using i128=__int128_t;
Xor_Basis<BIT*2,i128>tmp;
for(int i=0;i<=BIT;i++){
tmp.insert(((i128)lhs.data[i]<<BIT)|lhs.data[i]);
tmp.insert((i128)rhs.data[i]<<BIT);
}
Xor_Basis res;
for(int i=0; i<=BIT;i++){
if(tmp.data[i]){
res.data[i]=tmp.data[i];
res.cnt++;
}
}
res.zero=true;
return res;
}
};
template<typename T>
struct Trie{
vector<vector<int>>trie;
vector<int>pass;
vector<int>end;
int cnt;
int n;
int kind;
int base;
Trie(int _n,int _kind,int _base){
n=_n;
kind=_kind;
base=_base;
trie.assign(n,vector<int>(kind));
pass.assign(n,0);
end.assign(n,0);
cnt=1;
}
void insert(const T &s){
int cur=1;
pass[cur]++;
for(int i=30,path;i>=0;i--){
path=s>>i&1;
if(trie[cur][path]==0){
trie[cur][path]=++cnt;
}
cur=trie[cur][path];
pass[cur]++;
}
end[cur]++;
}
ll query(int x,int k)
{
ll ans=0;
int cur=1;
for(int i=30;i>=0;i--)
{
int path=x>>i&1;
if(k>>i&1)
{
ans+=pass[trie[cur][path]];
cur=trie[cur][path^1];
}
else
{
cur=trie[cur][path];
}
}
return ans+pass[cur];
}
};
void solve()
{
int n,m,k;
cin>>n>>m>>k;
vector<vector<pii>>g(n+1);
for(int i=1,u,v,w;i<=m;i++)
{
cin>>u>>v>>w;
g[u].push_back({v,w});
g[v].push_back({u,w});
}
Xor_Basis<30,int>base;
vector<int>path(n+1);
vector<int>vis(n+1);
auto dfs=[&](auto &&self,int u,int fa,int cur)->void
{
path[u]=cur;
vis[u]=1;
for(auto [v,w]:g[u])
{
if(v!=fa)
{
int nxt=cur^w;
if(vis[v])
{
base.insert(nxt^path[v]);
}
else
{
self(self,v,u,nxt);
}
}
}
};
dfs(dfs,1,0,0);
Trie<int>trie(31*n,2,0);
ll ans=0;
for(int i=1;i<=n;i++)
{
int cur=base.query_min(path[i]);
ans+=trie.query(cur,k);
trie.insert(cur);
}
cout<<ans<<endl;
}
void init()
{
}
signed main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t=1;
cin>>t;
init();
while(t--)
{
solve();
}
return 0;
}
首先是一个 Trick:对于图上异或问题,考虑构建生成树,然后将每个环的异或和插入线性基解决。
首先,在对整张图搞出一棵生成树后,从根节点开始递归,构建出从根节点到每个点 u 的异或和 。那么对于从 u 到 v 路径上的异或和,就是
,因为其 LCA 往上的部分可以异或消掉。
而对于非树边所构成的每个环,其都可以对这条路径的异或和产生影响。因为不管怎么去往每个环,最后回来的时候是可以把这条路上的权值异或消掉的。而每个环都可以选择去或者不去,那么问题就转化成了对于任意点对 (u,v),求 ,其中 s 是所有环的异或和任意组合,能搞出的某个异或和。
那么就有一个 Trick:对于异或任意组合的问题,就可以使用线性基解决。所以在将所有环的异或和插入线性基后,由于暴力枚举点对的复杂度是 O(n^2) 的,所以需要考虑优化。
由于线性基可以满足任意组合,所以考虑将 转化为
和
。可以证明两者是等价的,因为对于
的结果,因为想要和线性基组合搞出一个最小值,所以对于是 1 的位,肯定是要让其异或上线性基当前位消去的。那么由于这位是 1,就说明这位上
和
的状态必然不同。所以其单独和线性基组合必然是一个异或上线性基的当前位,一个不异或,所以是不会影响合并结果的,0 的情况类似。
那么在构建出 表示
的最小值后,此时问题就变为了计数有多少对 (i,j) 使得
。这个就可以通过将前缀
插入字典树,每次考察每一位求得。
具体方法是,在将所有前缀 插入字典树后,对于当前数
,去字典树上从高位到低位考察每一位。保证在遍历的过程中,考虑过的位和 K 相同。那么若当前位在 K 中是 1,此时既可以选 1 也可以选 0。若选择了 0,后续就可以任意选择,所以直接加上当前节点的 pass,然后去选 1 的情况往后考虑。否则,即当前位在 K 中是 0,那么就只能选 0 去后续考虑。最后注意要加上叶节点的 pass, 然后将当前
插入线性基即可。
总结
好不容易能补完一场 abc......