国旗计划
题目思路
首先发现环很难搞,尝试破环为链,将长度为 (m) 的环变成长度为 (2m) 的链
然后我们发现对于当前选择了区间 ([l_i,r_i]) 的情况来说,下一个选择的区间 ([l_d,r_j]) 必定在所有满足 (l_jle r_i) 的区间中 (r_j) 最大的
又因为区间之间两两互不包含,所以 (r_j) 最大的区间必定是 (l_j) 最大的区间,设该区间的编号为 (d_i)
于是我们先排序,再用双指针 (O(n)) 求出所有区间 (i) 的 (d_i)
接下来,对于必须包含区间 (i) 的询问,我们只需要不断跳 (d_i) 直到找到一个区间 (j) 满足 (r_j>l_i+m) 即可
( (+m) 是因为破环为链)
我们发现这样暴力跳的时间 (O(n^2)) 有点爆炸,考虑优化
我们发现这样从 (i) 一步步向前跳的算法一般可以用倍增来优化(可以参考LCA)
设 (d_{i,j}) 表示从 (i) 出发,跳 (2^j) 次到达的位置,预处理 (O(nlogn)) ,询问 (O(nlogn)),这道题就解决了。
注意破环为链时有些小细节要注意,具体可以参考代码
PS:据说有询问 (O(n)) 的做法,就是先求出不强制包含区间的答案 (ans),若询问的区间在答案中使用了就输出 (ans) ,否则就输出 (ans+1)
代码
#include<iostream>
#include<algorithm>
using namespace std;
int n,m,ans[200005];
int d[400005][20];
struct node {
long long l,r;
int id;
} a[400005];
bool cmp(node a1,node a2) {
return a1.l<a2.l;
}
int main() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++) {
a[i].id=i;
scanf("%d%d",&a[i].l,&a[i].r);
if(a[i].l>a[i].r)a[i].r+=m;
a[i+n].l=a[i].l+m,a[i+n].r=a[i].r+m;
}
sort(a+1,a+1+n*2,cmp);
for(int i=1,j=1; i<=n*2; i++) {
while(j<n*2&&a[j+1].l<=a[i].r)j++;
d[i][0]=j;
}
for(int j=1; j<20; j++) {
for(int i=1; i<=n*2; i++) {
d[i][j]=d[d[i][j-1]][j-1];
}
}
for(int i=1; i<=n; i++) {
int x=i;
for(int j=19; j>=0; j--) {
if(a[d[x][j]].r<a[i].l+m) {
x=d[x][j];
ans[a[i].id]+=(1<<j);
}
}
ans[a[i].id]+=2;
}
for(int i=1; i<=n; i++)printf("%d ",ans[i]);
return 0;
}
Blackout
题目思路
发现 (N^2) 的值很大,显然不能将网格弄出来
观察规则,发现如果将 ((x,y),(y,z)) 和 ((z,x)) 看成有向边的话,好像能形成一个三元环
我们考虑将网格看成有向图,将格子看成有向边,用三种颜色对这个有向图的所有弱连通分量按照 (0 ightarrow1,1 ightarrow2,2 ightarrow0) 的规则分别进行染色
发现染色后三种情况
-
无法进行染色(染色发生冲突)
显然此时出现了二元环或自环,随便画画图发现此时该连通分量所有边都会被加上
设点数为 (cnt_{node}) ,该连通分量的贡献即为 (cnt_{node}^2)
-
成功染色
- 没有颜色没被用过
显然对于所有 (2 ightarrow0) 的边都会由 (0 ightarrow1,1 ightarrow2) 的边产生,(0 ightarrow1,1 ightarrow2) 的边同理
设颜色为 (col) 的点数量为 (cnt_{col}),因此该连通分量的贡献即为 (cnt_0 imes cnt_1+cnt_1 imes cnt_2+cnt_2 imes cnt_0)
- 有颜色没被用过
显然除了该连通分量原本的边数外,不可能产生新的贡献,因此该连通分量的贡献即为其原本的边数
代码
#include<iostream>
#include<cstdio>
#include<vector>
using namespace std;
int n,m;
struct edge {
int nxt,to,val;
} e[200005];
int tot,head[100005];
void addedge(int u,int v,int w) {
e[++tot].to=v;
e[tot].val=w;
e[tot].nxt=head[u];
head[u]=tot;
}
bool flag;
long long ecnt,ncnt,h[5];
int c[100005];
void dfs(int u,int col) {
ncnt++;
h[c[u]=col]++;
for(int i=head[u]; i; i=e[i].nxt) {
int v=e[i].to,w=e[i].val;
ecnt++;
int ncol=(w?col%3+1:(col+1)%3+1);
if(c[v]&&c[v]!=ncol)flag=false;
if(c[v])continue;
dfs(v,ncol);
}
}
int main() {
scanf("%d%d",&n,&m);
while(m--) {
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y,1);
addedge(y,x,0);
}
long long ans=0;
for(int i=1; i<=n; i++) {
if(c[i])continue;
flag=true;
h[1]=h[2]=h[3]=0;
ncnt=ecnt=0;
dfs(i,1);
if(!flag)ans+=ncnt*ncnt;
else if(h[1]&&h[2]&&h[3])ans+=h[1]*h[2]+h[2]*h[3]+h[3]*h[1];
else ans+=ecnt/2;
}
printf("%lld",ans);
return 0;
}
Namori Grundy
题目思路
因为只有 (n) 个点 (n) 条边,而且每个点的入度显然都为 (0) (因为 ((p_i,i)) ),所以很明显这个有向图一定是一个环发散出一些边
我们暂时先不理会这个环,将这个环看作一个根,很明显这个有向图就是一棵外向树
既然是外向树,那么叶子节点的权值显然为 (0) ,除根外其他结点的权值直接按模拟即可求出
求出了除根外的其他结点,我们再来求这个根(环)
思考后发现这个环上的每一个节点只会有两种取值,我们来证明一下
设当前环上节点 (u) 指向的所有节点的集合为 (S_u),很明显 (S_u) 中只有一个节点 (v) 是同样在环上的,(S_u) 中的其他节点均已求出
我们再假设节点 (u) 指向的所有不在环上的节点的集合为 (s_u) ,那么 (u) 的一种可能取值 (a1_u) 即为 (mex {s_u}),也就是点 (u) 忽略环上的点 (v) 直接求出的权值,令一种 (a_u) 可能的取值 (a2_u) 即为当 (a_v) 的取值为 (a1_u) 时 (mex {S_u}),也就是点 (u) 不忽略环上的点 (v) 直接求出直接求出的权。可以发现当 (a_v) 的取值不为 (a1_u) 时,(a_u) 的取值一定为 (a1_u)
那么我们随便找一个点 (u) ,先暂时断开边 ((u,v)) ,将 (u) 的权值分别代入 (a1_u) 和 (a2_u) 后判断与 (a_v) 的关系是否合法即可
PS:我们发现,当且仅当这个环上所有点 (u) 的 (a1_u) 相等且为奇环时为 IMPOSSIBLE
,此时 (a_uin{a1_u,a1_u+1}) ,因此判断时代入权值 (a1_u) 和 (a1_u+1) 即可
代码
(考试时写的,有点丑陋
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm>
using namespace std;
int n,a[200005],d[200005];
vector<int>g[200005],g2[200005];
bool cmp(int g1,int g2) {
return a[g1]<a[g2];
}
queue<int>q;
int get(int u) {
sort(g[u].begin(),g[u].end(),cmp);
if(g[u].size()==0||a[g[u][0]]!=0) {
return 0;
} else {
for(int i=0; i<g[u].size(); i++) {
if(i==g[u].size()-1||a[g[u][i+1]]-a[g[u][i]]>1) {
return a[g[u][i]]+1;
}
}
}
}
vector<int>bb;
void run(int s) {
while(!q.empty()) {
int u=q.front();
q.pop();
for(int i=0; i<g2[u].size(); i++) {
int v=g2[u][i];
d[v]--;
if(s)bb.push_back(v);
if(d[v]==0)q.push(v);
}
if(u!=s)a[u]=get(u);
}
}
void back(){
for(int i=0;i<bb.size();i++){
d[bb[i]]++;
}
}
bool solve(int u) {
int v;
d[u]--;
for(int i=0; i<g[u].size(); i++) {
if(d[g[u][i]]) {
v=g[u][i];
g[u].erase(g[u].begin()+i);
break;
}
}
a[u]=get(u);
q.push(u);
run(u);
if(a[u]!=a[v])return true;
back();
a[u]=a[u]+1;
q.push(u);
run(u);
if(a[u]==a[v]+1)return true;
return false;
}
int main() {
scanf("%d",&n);
for(int i=1; i<=n; i++) {
int x;
scanf("%d",&x);
g[x].push_back(i);
g2[i].push_back(x);
d[x]++;
}
for(int i=1; i<=n; i++) {
if(d[i]==0)q.push(i);
}
run(0);
for(int i=1; i<=n; i++) {
if(d[i]) {
if(solve(i))printf("POSSIBLE");
else printf("IMPOSSIBLE");
return 0;
}
}
printf("POSSIBLE");
return 0;
}