1. Bouncing Boomerangs
链接 http://codeforces.com/problemset/problem/1428/D
分析
考虑到
a
[
i
]
<
=
3
a[i]<=3
a[i]<=3,先对放的障碍之间组合情况经行分析。
题目说明一行最多两个,分析两两之间的组合,
1
1
1要求右边同行无球,而
2
2
2则要求右边同行放
1
1
1,
3
3
3同行右边可放
2
,
1
2,\ 1
2, 1, 基于贪心,尽量先放
2
2
2再放
1
1
1,若找不到即为无解。
实现
基于上述分析,从右往左递推,同时维护一颗指针表示可以当下放的列,对于已经放入的球,可以用一个桶记录位置,与后续递推过程中的球进行组合。
参考代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=100010,M=8192,mod=1e9+7;
int n,m,k;
int a[N];
vector<pair<int,int>> s1,s2,s3;
void slove()
{
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
int r=n;
vector<pair<int,int>> res;
for(int i=n;i;--i)
{
if(!a[i]) continue;
if(a[i]==1)
{
res.emplace_back(r,i);
s1.emplace_back(r,i);
r--;
} else if(a[i]==2)
{
if(!s1.size()) return cout<<"-1",void();
auto t=s1.back(); s1.pop_back();
res.emplace_back(t.first,i);
s2.emplace_back(t.first,i);
} else
{
if(s1.size()==0&&s2.size()==0&&s3.size()==0) return cout<<"-1",void();
if(s3.size())
{
auto t=s3.back(); s3.pop_back();
res.emplace_back(r,i);
res.emplace_back(r,t.second);
s3.emplace_back(r--,i);
} else if(s2.size())
{
auto t=s2.back(); s2.pop_back();
res.emplace_back(r,i);
res.emplace_back(r,t.second);
s3.emplace_back(r--,i);
} else
{
auto t=s1.back(); s1.pop_back();
res.emplace_back(r,i);
res.emplace_back(r,t.second);
s3.emplace_back(r--,i);
}
}
}
cout<<res.size()<<"\n";
for(auto u:res) cout<<u.first<<' '<<u.second<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
while(_--) slove();
return 0;
}
2. Xor of 3
链接 http://codeforces.com/problemset/problem/1572/B
分析
操作前后异或和不变( 操作长度为奇数且连续 ),如果总体的异或和不为0,一定无解。
然后尝试利用该条件进行构造,发现可以对于奇数长度的可以先将异或和递推集中到最后的三个数上,这三个数均变成0,然后有因为递推过程中的相邻数相同,再进行逆向操作,能使得所有数变为0, 总的操作次数
(
n
−
3
)
/
2
∗
2
+
1
=
n
−
2
(n-3)/2*2+1\ = \ n-2
(n−3)/2∗2+1 = n−2。
对于偶数,递推过程中,一定有在奇数位置的数被操作两次,即整个异或和被分成两段,前方异或和与后方异或和为0。
我们尝试找到一个奇数长度得前缀异或和为0,然后分成两部分经行上述操作,如果找不到即为无解。
参考代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=200010,M=8192,mod=998244353;
int n,m,k;
int a[N];
vector<int> res;
void work(int l,int r)
{
for(int i=l;i+2<=r;i+=2)
res.emplace_back(i);
for(int i=r-4;i>=l;i-=2)
res.emplace_back(i);
}
void slove()
{
res.clear();
cin>>n;
for(int i=1;i<=n;++i) cin>>a[i];
int pre=0;
for(int i=1;i<=n;++i) pre^=a[i];
if(pre) return cout<<"NO\n",void();
if(n&1)
{
work(1,n);
cout<<"YES\n";
cout<<res.size()<<"\n";
for(auto u:res) cout<<u<<' ';
if(res.size()) cout<<"\n";
} else
{
int pre=0;
for(int i=1;i<=n;++i)
{
pre^=a[i];
if(pre==0&&(i&1))
{
work(1,i); work(i+1,n);
cout<<"YES\n";
cout<<res.size()<<"\n";
for(auto u:res) cout<<u<<' ';
if(res.size()) cout<<"\n";
return ;
}
}
cout<<"NO\n";
}
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
3. Aroma’s Search
链接 http://codeforces.com/problemset/problem/1292/B
分析
发现数据大,且操作没有贪心性,然后再分析,发现 a x > = 2 a_x>=2 ax>=2 ,点的分布成指数级别,即要分析的点的数量是有限的, 整体数量不到 80 80 80。然后再进一步分析,要取得点是连续的一段,我们可以枚举起点和终点,时间复杂度是可以保证的,注意实现的时候无序枚举,因为起点和终点没有最优性,然后考虑如何维护合法,这里因为点的分布的递增,从起点到终点的曼哈顿距离即为所求,在这内部的点,我们都能找到一条路径走到。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =100010,mod=998244353;
int n,m,k;
vector<pair<ll,ll>> v;
void slove()
{
ll x0,y0,ax,ay,bx,by,xs,ys,t;
cin>>x0>>y0>>ax>>ay>>bx>>by>>xs>>ys>>t;
while(x0<=3e16&&y0<=3e16)
{
v.emplace_back(x0,y0);
x0=x0*ax+bx,y0=y0*ay+by;
}
int ans=0;
n=v.size();
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
{
if(abs(v[i].first-v[j].first)+abs(v[i].second-v[j].second)+abs(v[i].first-xs)+abs(v[i].second-ys)<=t)
ans=max(ans,abs(i-j)+1);
}
cout<<ans<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
4. Edge Weight Assignment
链接 http://codeforces.com/problemset/problem/1338/B
分析
最少的方式可以从树是二分图这个角度分析,
n
>
=
3
n>=3
n>=3表示叶子的深度
>
=
2
>=2
>=2, 如果叶子的深度均为偶数或奇数,基于对称性,可以都填一样的,否则填
3
3
3种,可以令这两类分别为1, 2,令根为3。
最多的方式从路径层面分析,两片叶子到$\ lca\
,我们可以构造路径上的权值不同,
,我们可以构造路径上的权值不同,
,我们可以构造路径上的权值不同,lca$到根的路径上权值也可以不同,这样我们只要求将属于同一片区域的叶子看成一片即为所求。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N =100010,mod=998244353;
int n,m,k;
int in[N],de[N];
vector<int> g[N];
int ans;
bool n1=false,n2=false;
int dfs(int u,int fa)
{
de[u]=de[fa]+1;
if(g[u].size()==1)
{
if(de[u]&1) n1|=true;
else n2|=true;
return 1;
}
int pre=0;
for(auto j:g[u])
{
if(j==fa) continue;
pre+=dfs(j,u);
}
ans-=max(0,pre-1); // 叶子权值选择为1种
return 0;
}
void slove()
{
cin>>n;
for(int i=1;i<n;++i)
{
int a,b; cin>>a>>b;
g[a].emplace_back(b);
g[b].emplace_back(a);
in[a]++,in[b]++;
}
for(int i=1;i<=n;++i)
{
if(in[i]>=2)
{
dfs(i,0);
break;
}
}
if(n1&&n2) cout<<"3 ";
else cout<<"1 ";
cout<<ans+n-1<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
5. Perform Easily
链接 http://codeforces.com/problemset/problem/1413/C
分析
无发现最优策略。
a
a
a的长度有限,考虑枚举答案。将所有结果存数组里,排序然后双指针移动同时记录状态维护合法性和最优性。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(int)b;++i)
using namespace std;
typedef long long ll;
const int N =500010,mod=998244353;
int n,m,k;
ll a[N],b[N];
int f[N];
void slove()
{
for(int i=0;i<6;++i) cin>>a[i];
cin>>n;
vector<pair<int,int>> v;
for(int i=0;i<n;++i)
{
int val; cin>>val;
for(int j=0;j<6;++j)
v.emplace_back(val-a[j],i);
}
sort(all(v));
int sz=v.size(),now=0;
ll ans=1ll<<60;
for(int l=0,r=0;r<sz;++r)
{
if(++f[v[r].second]==1) now++;
while(f[v[l].second]>1) f[v[l++].second]--;
if(now==n) ans=min(ans,(v[r].first-v[l].first)*1ll);
}
cout<<ans<<"\n";
}
int main()
{
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
6. Tree Array
链接 http://codeforces.com/problemset/problem/1540/B
分析
发现对于一个树,这个过程与起点有关,又n比较小,可以考虑枚举根,对于一棵树,基于期望的线性性的,我们分析两个点之间的贡献计算,然后枚举点对进行计算。
分析两个点之间的贡献,如果一个点是另一个点的父亲那么答案一定,若不是,转化问题即每个点都有对等的概率往上走一步,谁先到达
l
c
a
lca
lca, 这里我们可以同时递推的计算实现,更进一步,我们可以预处理所有结果出来, 这里我们能用预处理维护的的是距离而非具体的点,便于我们对多棵树进行计算,那么我们还需要实现找到
l
c
a
lca
lca.
参考代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
#define low(x) (x&-x)
using namespace std;
typedef long long ll;
const int N=210,M=8192,mod=1e9+7;
int n,m,k;
vector<int> g[N];
int f[N][N];
int de[N],w[N][15];
int ksm(int a,int b,int res=1)
{
for(;b;b>>=1,a=1ll*a*a%mod)
if(b&1) res=1ll*res*a%mod;
return res;
}
void dfs(int u,int fa)
{
de[u]=de[fa]+1;
w[u][0]=fa;
for(int k=1;k<=14;++k)
w[u][k]=w[w[u][k-1]][k-1];
for(auto j:g[u])
{
if(j==fa) continue;
dfs(j,u);
}
}
int lca(int a,int b)
{
if(de[a]<de[b]) swap(a,b);
for(int k=14;~k;--k)
{
if(de[w[a][k]]>=de[b]) a=w[a][k];
}
if(a==b) return a;
for(int k=14;~k;--k)
{
if(w[a][k]!=w[b][k])
a=w[a][k],b=w[b][k];
}
return w[a][0];
}
int get(int root)
{
dfs(root,0);
int res=0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
{
int t=lca(i,j);
res+=f[de[i]-de[t]][de[j]-de[t]];
res%=mod;
}
return res;
}
void slove()
{
cin>>n;
for(int i=1;i<=n;++i) f[i][0]=1; // 预处理
for(int i=1;i<=n;++i) f[0][i]=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
f[i][j]=1ll*(f[i-1][j]+f[i][j-1])%mod*(mod+1>>1)%mod;
for(int i=1;i<n;++i)
{
int a,b; cin>>a>>b;
g[a].emplace_back(b);
g[b].emplace_back(a);
}
int res=0;
for(int i=1;i<=n;++i) res+=get(i),res%=mod;
cout<<res*1ll*ksm(n,mod-2)%mod;
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
while(_--) slove();
return 0;
}
7. Removing Leaves
链接 http://codeforces.com/problemset/problem/1385/F
分析
如果一个非叶子节点能变成叶子节点,那么叶子节点数一定是 k k k的倍数。操作顺序是无关的的,只要能操作就操作即为最优解。考虑实现可以用类似拓扑排序的方式是实现,具体可参考代码,过程中维护两个变量。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;
int n,m,t;
vector<int> g[N];
int st[N],pre[N],is[N];
void slove()
{
int k;
cin>>n>>k;
for(int i=1;i<=n;++i)
g[i].clear(),pre[i]=0,is[i]=0,
st[i]=0;
for(int i=1;i<n;++i)
{
int a,b; cin>>a>>b;
g[a].emplace_back(b),
g[b].emplace_back(a);
st[a]++,st[b]++;
}
int res=0;
queue<int> q;
for(int i=1;i<=n;++i)
if(st[i]==1)
q.emplace(i);
while(q.size())
{
int t=q.front();
q.pop();
is[t]=1;
for(auto u:g[t])
{
if(is[u]) continue;
pre[u]++;
if(pre[u]==k) pre[u]=0,res++;
--st[u];
if(st[u]==1&&pre[u]==0)
q.emplace(u);
}
}
cout<<res<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
8. Divide Square
链接 http://codeforces.com/problemset/problem/1401/E
分析
对于这类问题,通常是分析交点数与增加面之间的关系。本题中的点可以分成来给两类,第一类是与边界的交点,第二类是内部的交点-即给定的线段与线段之间的交点。前者特判,后者二维偏序问题,手动差分维护一侧的贡献,另一侧的贡献由树状数组处理区间查询。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define int ll
using namespace std;
typedef long long ll;
const int N=1000010;
int n,m,k;
vector<pair<int,int>> c[N],q[N];
ll tr[N];
void add(int x,int c)
{
while(x<=1e6+1) tr[x]+=c,x+=low(x);
}
ll query(int x,ll res=0)
{
while(x) res+=tr[x],x-=low(x);
return res;
}
void slove()
{
cin>>n>>m;
ll res=1;
for(int i=1;i<=n;++i)
{
int x,l,r; cin>>x>>l>>r;
l++,r++; x++;
if(l==1&&r-1==1000000) res++;
c[l].emplace_back(x,1);
c[r+1].emplace_back(x,-1);
}
for(int i=1;i<=m;++i)
{
int x,l,r; cin>>x>>l>>r;
l++,r++; x++;
if(l==1&&r-1==1000000) res++;
q[x].emplace_back(l-1,r);
}
for(int i=1;i<=1e6+1;++i)
{
for(auto u:c[i])
add(u.first,u.second);
for(auto u:q[i])
res+=query(u.second)-query(u.first);
}
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
9. Ball
链接 https://acm.hdu.edu.cn/showproblem.php?pid=7141
分析
最暴力的方式是枚举三个点,然后尝试优化,因为是中位数,我们把所有边长放一起然后排序,对于一条边长,我们能确定两个点,且一条边的边长大于当前边,另一条边小于,实际上经过排序处理后这两部分是对称的,我们记录前方与当前点的匹配状态,如果前面的一个点没有与当前点同时连上即为所求,可以用异或运算解决。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=200010;
int n,m,k,q;
int a[N],b[N];
bitset<2000> s[2021];
int st[N],ip[N],idx;
void inti(int n)
{
st[1]=true;
for(int i=2;i<=n;++i)
{
if(!st[i]) ip[idx++]=i;
for(int j=0;ip[j]<=n/i;++j)
{
st[ip[j]*i]=1;
if(i%ip[j]==0) break;
}
}
}
void slove()
{
cin>>n>>m;
for(int i=0;i<n;++i) cin>>a[i]>>b[i];
for(int i=0;i<n;++i) s[i].reset();
vector<tuple<int,int,int>> v;
for(int i=0;i<n;++i)
for(int j=i+1;j<n;++j)
v.emplace_back(abs(a[i]-a[j])+abs(b[i]-b[j]),i,j);
sort(all(v));
ll res=0;
for(auto u:v)
{
int x,y,dis; tie(dis,x,y)=u;
// cout<<dis<<' '<<x<<' '<<y<<endl;
if(!st[dis]) res+=(s[x]^s[y]).count();
s[x][y]=1,s[y][x]=1;
}
cout<<res<<"\n";
}
signed main()
{
// cin.tie(nullptr)->ios::sync_with_stdio(false);
inti(N-1);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
10. Laser
链接 https://acm.hdu.edu.cn/showproblem.php?pid=7146
分析
注意到中心的确立,三个不在同一条直线上的点能确定一个中心,暴力做法是 n 3 n^3 n3, 因为这里方向有限,我们考虑枚举方向,然后找到不在这个方向上的点,可以得到三个可能的中心,然后暴力 c h e c k check check, 这里枚举方向可以用坐标放缩变换实现。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=1000010;
int n,m,k,q;
int a[N],b[N],x[N],y[N];
bool go(int cx,int cy)
{
for(int i=1;i<=n;++i)
{
int dx=cx-x[i],dy=cy-y[i];
if(dx&&dy&&(dx!=dy)&&(dx+dy!=0)) return false;
}
return true;
}
bool check()
{
for(int i=2;i<=n;++i)
{
if(x[i]==x[1]) continue;
int lef=x[i]-x[1];
if(go(x[1],y[i])) return true;
if(go(x[1],y[i]+lef)) return true;
if(go(x[1],y[i]-lef)) return true;
return false;
}
return true;
}
void slove()
{
cin>>n;
bool nic=false;
for(int i=1;i<=n;++i) cin>>a[i]>>b[i];
for(int i=1;i<=n;++i) x[i]=a[i],y[i]=b[i];
nic|=check();
for(int i=1;i<=n;++i) x[i]=b[i],y[i]=a[i];
nic|=check();
for(int i=1;i<=n;++i) x[i]=a[i]-b[i],y[i]=a[i]+b[i];
nic|=check();
for(int i=1;i<=n;++i) x[i]=a[i]+b[i],y[i]=a[i]-b[i];
nic|=check();
if(nic) cout<<"YES\n";
else cout<<"NO\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
11. Path
链接 https://acm.hdu.edu.cn/showproblem.php?pid=7145
分析
最短路问题,边权大于等于 0 0 0, 分析点的扩展一个是 d i j k s t r a dijkstra dijkstra, 另一个是暴力枚举,可以每个状态用一个set维护,但时间和空间上都不允许,分析剪枝,操作二能走到的0边权的点一定是最优解,可以搭配bfs的扩展理解,所以我们维护一个全局set,维护操作2能遍历到的点,操作过程中注意删除。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=1000010;
int n,m,k,q;
ll dis[N][2],st[N][2],mas[N];
vector<tuple<int,int,int>> g[N];
set<int> h;
void slove()
{
int s;
cin>>n>>m>>s>>k;
h.clear();
for(int i=1;i<=n;++i) mas[i]=false,g[i].clear();
for(int i=1;i<=n;++i)
for(int j=0;j<2;++j)
dis[i][j]=1ll<<60,
st[i][j]=false;
while(m--)
{
int x,y,w,t; cin>>x>>y>>w>>t;
g[x].emplace_back(y,w,t);
}
priority_queue<tuple<int,int,int>,vector<tuple<int,int,int>>,greater<tuple<int,int,int>>> q;
dis[s][0]=0;
q.emplace(0,s,0);
int cnt=0;
for(int i=1;i<=n;++i) h.insert(i);
while(q.size())
{
int ver,f; tie(ignore,ver,f)=q.top();
q.pop();
if(st[ver][f]) continue;
st[ver][f]=true;
h.erase(ver);
if(f)
{
++cnt;
for(auto u:g[ver])
{
int to,w,go; tie(to,w,go)=u;
mas[to]=cnt;
}
vector<int> tmp;
for(auto u:h)
{
if(mas[u]==cnt) continue;
dis[u][0]=dis[ver][f];
q.emplace(dis[u][0],u,0);
tmp.emplace_back(u);
}
for(auto u:tmp) h.erase(u);
}
int t=f?-k:0;
for(auto u:g[ver])
{
int to,w,go; tie(to,w,go)=u;
if(dis[to][go]>dis[ver][f]+w+t)
{
dis[to][go]=dis[ver][f]+w+t;
q.emplace(dis[to][go],to,go);
}
}
}
for(int i=1;i<=n;++i)
cout<<(((dis[i][1]==1ll<<60&&dis[i][0]==1ll<<60)?-1:min(dis[i][1],dis[i][0])))<<" ";
cout<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
12. Boss Rush
链接 https://acm.hdu.edu.cn/showproblem.php?pid=7163
分析
n = 18 n=18 n=18, 应当是一个状态压缩,如果直接搜索,既没有最优的可维护的性质,同时过程中变量过多不好维护。这里运用二分时间,这样状态可以维护当下时间最大伤害值,因为我们枚举一次操作后,剩下的时间是可以确定的,少了一层变量的干扰。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int N=1E6+10,mod=998244353;
int n,m,k;
ll h;
ll d[20][N],f[1<<19],t[N],len[N];
ll dfs(int mas,ll now,ll st)
{
if(now>=st) return 0;
if(f[mas]) return f[mas];
ll res=0;
for(int i=0;i<n;++i)
if(~mas>>i&1)
{
res=max(res,d[i][min(len[i],st-now)]+dfs(mas|1<<i,now+t[i],st));
}
return f[mas]=res;
}
bool check(int x)
{
for(int i=0;i<1<<n;++i) f[i]=0;
return dfs(0,0,x)>=h;
}
void slove()
{
cin>>n>>h;
for(int i=0;i<n;++i)
{
cin>>t[i]>>len[i];
for(int j=1;j<=len[i];++j) cin>>d[i][j];
rep(j,1,len[i]+1) d[i][j]+=d[i][j-1];
}
int l=0,r=1e7;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<(l==1e7?-1:max(l-1,0))<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
13. Link with Level Editor II
链接 https://acm.hdu.edu.cn/showproblem.php?pid=7178
分析
n n n比较小,先分析暴力的做法,对于一个固定左端点,右端点具有单调性,同理对于右端点,可以用双指针维护,那么如何维护任意一段的方案数,这里 m m m比较小,我们可以用矩阵维护整张图的方案数,然后用线段树经行区间查询。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
#define rep(i,a,b) for(int i=a;i<(b);++i)
using namespace std;
typedef long long ll;
const int N=5e3+10,mod=998244353;
int n,m,mx;
ll f[20];
struct M {
ll s[20][20];
M() {
memset(s,0,sizeof s);
}
void inti() {
for(int i=0;i<20;++i)
rep(j,0,20)
if(i==j) s[i][j]=1;
else s[i][j]=0;
}
M operator+(const M &c) const {
M a=*this,b;
rep(i,0,20)
rep(j,0,20)
rep(z,0,20)
b.s[i][j]+=a.s[i][z]*c.s[z][j];
return b;
}
} tr[N<<2];
void mul(M c) {
static ll ans[20];
memset(ans,0,sizeof ans);
for(int i=0;i<m;++i)
for(int z=0;z<m;++z)
ans[i]+=f[z]*c.s[z][i];
// rep(i,0,20) cout<<ans[i]<<" \n"[i==20-1];
memcpy(f,ans,sizeof ans);
}
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
void up(int u) {
tr[u]=tr[ls]+tr[rs];
}
void build(int u,int l,int r)
{
if(l==r) {
tr[u].inti();
int cnt; cin>>cnt;
while(cnt--) {
int a,b; cin>>a>>b;
a--,b--;
tr[u].s[a][b]++;
}
return ;
}
build(ls,l,mid),build(rs,mid+1,r);
up(u);
}
void query(int u,int l,int r,int ql,int qr)
{
if(ql<=l&&qr>=r) mul(tr[u]);
else {
if(ql<=mid) query(ls,l,mid,ql,qr);
if(qr>mid) query(rs,mid+1,r,ql,qr);
}
}
bool check(int l,int r) {
for(int i=0;i<m;++i) f[i]=0;
f[0]=1;
query(1,1,n,l,r);
return f[m-1]<=mx;
}
void slove()
{
cin>>n>>m>>mx;
build(1,1,n);
int res=0;
for(int i=1,j=1;i<=n;++i) {
j=max(j,i);
while(j<=n&&check(i,j)) j++;
res=max(j-i,res);
}
cout<<res<<"\n";
}
signed main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
13. Instant Noodles
链接 http://codeforces.com/problemset/problem/1322/C
分析
集合数量过多,分析点的贡献,若右边两点从属的集合,如果完全相同,那这两个点可以看成一个整体,否则,那么答案为一下几种 g c d ( a 1 , a 2 ) , g c d ( a 1 , a 2 + a 1 ) , g c d ( a 1 + a 2 , a 2 ) , g c d ( a 1 + a 2 , a 1 , a 2 ) gcd(a_1,\ a_2), gcd(a_1, a_2+a_1), gcd(a_1+a_2, a_2), gcd(a_1+a_2, a_1, a_2) gcd(a1, a2),gcd(a1,a2+a1),gcd(a1+a2,a2),gcd(a1+a2,a1,a2), 实际上通过 g c d gcd gcd的运算性质,这些答案运算结果是一样。考虑多个集合之间,利用gcd运算的传递性,可以通过递推的方式实现多个集合之间的运算。
参考代码
#include<bits/stdc++.h>
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;
int n,m,t;
ll w[N];
set<int> v[N];
void slove()
{
map<set<int>,ll> h;
cin>>n>>m;
for(int i=1;i<=n;++i) v[i].clear();
for(int i=1;i<=n;++i) cin>>w[i];
while(m--)
{
int a,b; cin>>a>>b;
v[b].insert(a);
}
for(int i=1;i<=n;++i)
if(v[i].size())
h[v[i]]+=w[i];
ll res=0;
for(auto u:h)
res=__gcd(res,u.second);
cout<<res<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
cin>>_;
while(_--) slove();
return 0;
}
14. Xor Tree
链接 http://codeforces.com/problemset/problem/1446/C
分析
根据异或运算,可以从拆位,线性基,字典树方向考虑,
这里对比一下后用字典树分析,那么操作等价于不存在一颗子树大小大于1,对于一个节点,我们有两种决策,可以通过递归方式实现,而时间复杂度,类比线段树,总体合法的。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N =200010,M=N*30*2;
int n,m,k;
int tr[M][2],cnt,sz[M];
void add(int x)
{
int p=0;
for(int i=30;~i;--i)
{
int t=x>>i&1;
if(!tr[p][t]) tr[p][t]=++cnt;
p=tr[p][t]; sz[p]++;
}
}
int dfs(int node,int d)
{
if(d==-1) return 0;
if(!tr[node][1]) return dfs(tr[node][0],d-1);
if(!tr[node][0]) return dfs(tr[node][1],d-1);
return min(dfs(tr[node][0],d-1)+sz[tr[node][1]]-1,dfs(tr[node][1],d-1)+sz[tr[node][0]]-1);
}
void slove()
{
cin>>n;
for(int i=1;i<=n;++i)
{
int val; cin>>val;
add(val);
}
cout<<dfs(0,30)<<"\n";
}
int main()
{
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}
15. Avoid Rainbow Cycles
链接 http://codeforces.com/problemset/problem/1408/E
分析
首先是建图,由于边的数量是 N 2 N^2 N2, 我们可以通过建立一个虚拟源点维护每个集合。然后分析操作,从规模小开始分析,对于两个集合,两者相交元素数量大于等于2即为不合法,即在建图后是两颗树/或通过一个节点连接两棵树后形成一棵树。然后再加入一个集合,继续分析,可以发现总体的形态是一片森林。我们尝试维护这片森林,可以用 k r u s k a l kruskal kruskal算法的方式实现。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
#define all(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
const int N =200010,M=N*30*2;
int n,m,k;
vector<tuple<int,int,int>> q;
int fa[N],a[N],b[N];
int find(int x)
{
return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
void slove()
{
cin>>m>>n;
for(int i=1;i<=m;++i) cin>>a[i];
for(int i=1;i<=n;++i) cin>>b[i];
iota(fa,fa+1+n+m,0);
ll res=0;
for(int i=1;i<=m;++i)
{
int cnt; cin>>cnt;
while(cnt--)
{
int x; cin>>x;
int w=a[i]+b[x];
res+=w;
q.emplace_back(w,x,n+i);
}
}
sort(all(q),greater<tuple<int,int,int>>());
for(auto u:q)
{
int w,x,y; tie(w,x,y)=u;
x=find(x),y=find(y);
if(x!=y) res-=w,fa[x]=y;
}
cout<<res<<"\n";
}
int main()
{
int _=1;
while(_--) slove();
return 0;
}
16. Fixed Point Removal
链接 http://codeforces.com/problemset/problem/1404/C
分析
先分析操作,一开始对 a [ i ] = i a[i] = i a[i]=i进行操作,然后可以对 a [ i ] = i + 1 , a [ i ] = i a[i]=i+1,\ a[i]=i a[i]=i+1, a[i]=i,同时注意到位置,从后出发,可以把这些点都除去,然后再分析,可以得到一个结论对于 a [ i ] > = i a[i]>=i a[i]>=i,都有可能被除去,然后考虑实现,对于一个贡献点,我们处理左端点的贡献,把该点贡献加到该点上,然后处理右边界,可以通过离线处理,对于查询区间贡献,可以用树状数组实现,而查询贡献点,实际上等价于查询贡献点的第k大,同样可以用树状数组解决。
参考代码
#include<bits/stdc++.h>
#define low(x) (x&-x)
using namespace std;
typedef long long ll;
const int N=500010,mod=998244353;
int n,m,t;
int a[N];
vector<pair<int,int>> q[N];
int tr[N],ans[N];
void add(int x,int c)
{
while(x<=n) tr[x]+=c,x+=low(x);
}
int query(int x,int res=0)
{
while(x) res+=tr[x],x-=low(x);
return res;
}
int kth(int x,int lim,int pos=0)
{
for(int i=1<<20;i;i>>=1)
{
if(pos+i<=lim&&x>tr[pos+i])
pos+=i,x-=tr[pos];
}
return pos+1;
}
void slove()
{
int k;
cin>>n>>k;
for(int i=1;i<=n;++i) cin>>a[i];
for(int i=1;i<=k;++i)
{
int l,r; cin>>l>>r;
r=n-r;
q[r].emplace_back(l,i);
}
for(int i=1;i<=n;++i)
{
int d=i-a[i];
int t=query(i);
int pos=t-d+1;
if(!d) add(i,1);
else if(d>=0&&pos>0) add(kth(pos,i),1);
for(auto u:q[i])
ans[u.second]=query(i)-query(u.first);
}
for(int i=1;i<=k;++i) cout<<ans[i]<<"\n";
}
int main()
{
cin.tie(nullptr)->ios::sync_with_stdio(false);
int _=1;
// cin>>_;
while(_--) slove();
return 0;
}