前言
又是两场,感觉一般的内容已经基本可以解决了,欠缺一点稍微复杂点的数据结构的能力。今天把几道题说一下(其实早就搞定了
位运算的解题原则
就和名字一样,位运算,大部分的题目只需按位进行分析即可,突出特点是两位之间一般没有联系。有联系的一般和其他知识相关联。
第7场
xay loves trees
题目大意:有两颗树,一颗是相容树,一颗是互斥树,输出满足这两颗树的最大集。
如果谈到最大集而非集合的数目,也就是说可以利用搜索的方式进行解决,也就是求得最优解的过程,是较为简单的。
本题关键在第二棵树,将一个点与其他点关联起来,然后利用第一颗树进行dfs就可以解决。
而第二点在于如何快速的收集这个结果。已知每个点最多有且只能有一个在集合中,也就是讲:如果最大值不为1,则非法。因此选用树状数组的方式进行询问(树上滑窗?
而问题在于,为什么不在更新时直接利用2进行检测呢?因为单次数据的插入是更新整片的过程,如果在更新时利用2进行直接检测,则一会破坏运算的连续性,二则在于更新时不好写(懒),最终写出的代码也是非常难看的,涉及到多个不连续更新过程。
因此,本题代码。
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 10;
int cnt, ans, vis[N], b[N], sz[N], num[N], dfn[N], n;
deque<int> q;
vector<int> edge[N], edge2[N];
int w[N];
struct Node
{
int l, r, mx, add;
}tr[N * 4];
void pushup(int u) {
tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
}//mx为收集上来的最大值
//
void pushdown(int u)
{
auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
if (root.add)
{
left.add += root.add, left.mx += root.add;
right.add += root.add, right.mx += root.add;
root.add = 0;
}
}
void build(int u, int l, int r)
{
if (l == r) tr[u] = {l, r, 0, 0};
else
{
tr[u] = {l, r};
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
pushup(u);
}
}
//更新范围内所有的结点
void modify(int u, int l, int r, int d)
{
if (tr[u].l >= l && tr[u].r <= r) {
tr[u].mx += d;
tr[u].add += d;
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, d);
if (r > mid) modify(u << 1 | 1, l, r, d);
pushup(u);
}
}
//询问区间内最大值
//最开始问的是1,也就是最大的那个区间
int query(int u, int l, int r) {
//在范围内
if (tr[u].l >= l && tr[u].r <= r) return tr[u].mx;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
int mx = 0;
if (l <= mid) mx = max(mx, query(u << 1, l, r));
if (r > mid) mx = max(mx, query(u << 1 | 1, l, r));
return mx;
}
//对可行集的遍历
void dfs(int u) {
for (int v : edge[u])
if (!vis[v]) {
vis[v] = 1;
//更新联通集大小
//拿到互斥集此点对应的边到互斥联通集,更新所有结点
//看看这个家伙有没有亲戚,有亲戚就改一下,最好是孤儿
//-1是自己,自己是这家伙爹,不能死了,所以要-1//好在是个DAG
modify(1, num[v], num[v] + sz[v] - 1, 1);//1 1 n 1//u l r d
//孤儿就是叶子结点或孤立结点
if (query(1, 1, n) >= 2){//看看是不是孤儿
int w = q.front();
modify(1, num[w], num[w] + sz[w] - 1, -1);
q.pop_front();
q.push_back(v);
dfs(v);
modify(1, num[w], num[w] + sz[w] - 1, 1);//再搞回来,继续搜这个孤儿
q.push_front(w);
} else {//是孤儿,可以更新
q.push_back(v);
ans = max(ans, (int)q.size());//获取结果的最大非互斥集大小
dfs(v);//可以进行下一个结点的检测
}
q.pop_back();
//把刚刚更新的再删掉,避免-之类的
modify(1, num[v], num[v] + sz[v] - 1, -1);
}
}
void dfs1(int u) {
dfn[++cnt] = u;//边对点
num[u] = cnt;//点对边
sz[u] = 1;//互斥集联通
for (int v :edge2[u])//遍历edge2非可行集
if (!vis[v]){
vis[v] = 1;
dfs1(v);
sz[u] += sz[v];
}
}
int main() {
int tt;
cin >> tt;
while (tt--) {
cin >> n;
build(1, 1, n);
cnt = 0;
for (int i = 1; i <= n; i++) {
edge[i].clear();
edge2[i].clear();
}
for (int i = 2; i <= n; i++) {
int u, v;
cin >> u >> v;
edge[u].push_back(v);
edge[v].push_back(u);
}
for(int i = 2; i <= n; i++) {
int u, v;
cin >> u >> v;
edge2[u].push_back(v);
edge2[v].push_back(u);
}
memset(vis, 0, sizeof(vis));
vis[1] = 1;
dfs1(1);//求互斥集联通集大小-以1为根
//for (int i = 1; i <= n; i++) cout << num[i] << '\n';
memset(vis, 0, sizeof(vis));
ans = 1;
q.clear();
q.push_back(1);
modify(1, 1, n, 1);
vis[1] = 1;
dfs(1);
cout << ans << '\n';
}
return 0;
}
Xray loves Floyd
题目大意:计算后固定k的正确个数。
解:先用dij解出直连的正确个数,然后考虑到如果有一个路径在最短路径上时也是可行的,因此进行dij还原,得到结果。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005,inf=0x3f3f3f3f;
int w[maxn][maxn],n;
vector<pair<int,int>> g[maxn];
void dij(int s,int *dis)
{
priority_queue< pair<int,int>,vector<pair<int,int> >, greater<pair<int,int>> >q;
q.push(make_pair(0,s));
dis[s]=0;
int x,y,k;
while(!q.empty()){
tie(k,x)=q.top(); q.pop();
for(auto it:g[x]){
y=it.first; k=it.second;
if(dis[y]>dis[x]+k){
dis[y]=dis[x]+k;
q.push(make_pair(dis[y],y));
}
}
}
}
vector<int>q[maxn];
int ok[maxn][maxn];
int main()
{
int x,y,k,m;
scanf("%d%d",&n,&m);
while(m--){
scanf("%d%d%d",&x,&y,&k);
g[x].push_back(make_pair(y,k));
}
memset(w,0x3f,sizeof(w));
for(int i=1;i<=n;i++)dij(i,w[i]);
for(int i=1;i<=n;i++)
for(auto gg:g[i]){
if(w[i][gg.first]==gg.second)q[i].push_back(gg.first),ok[i][gg.first]=1;
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
if(ok[i][j]||i==j||w[i][j]==inf){
ans++; continue;
}
for(auto it:q[i])if(ok[it][j]&&w[i][j]==w[i][it]+w[it][j]){
ans++; q[i].push_back(j); ok[i][j]=1; break;
}
}
}
printf("%d\n",ans);
return 0;
}
第8场脑筋急转弯比较多,不写日志了。