链接:https://ac.nowcoder.com/acm/contest/888/A
来源:牛客网
Gromah and LZR entered the great tomb, the first thing they see is a matrix of size n × m n×m n×m times mn×m, and the elements in the matrix are all 0 or 1.
LZR finds a note board saying “An all-one matrix is defined as the matrix whose elements are all 1, you should determine the number of all-one submatrices of the given matrix that are not completely included by any other all-one submatrices”.
Meanwhile, Gromah also finds a password lock, obviously the password should be the number mentioned in the note board!
Please help them determine the password and enter the next level.
The first line contains two positive integers n,m, denoting the size of given matrix.
Following n lines each contains a string with length m, whose elements are all 0 or 1, denoting the given matrix.
1 ≤ n , m ≤ 3000 1≤n,m≤3000 1≤n,m≤3000
Print a non-negative integer, denoting the answer.
3 4
0111
1110
0101
5
The 5 matrices are (1,2)−(1,4), (1,2)−(2,3), (1,2)−(3,2), (2,1)−(2,3), (3,4)−(3,4).
题目的大意是给出一个01矩阵,要找出有多少个全1矩阵不是其他全1矩阵的子矩阵。
官方题解给出来的方法是对每个格子 ( i , j ) (i,j) (i,j),记录一个 U p [ i ] [ j ] Up[i][j] Up[i][j]表示这个格子向上的连续1的个数,之后枚举每一行作为矩阵的底边所在行,从左向右枚举这一行,维护一个 U p [ i ] [ j ] Up[i][j] Up[i][j]单调上升的栈,对于栈中的每个 U p Up Up值再维护一个它向左最远能够拓展到的位置 p o s pos pos。这样每当有元素 ( U p , p o s ) (Up,pos) (Up,pos)退栈的时候,就能够获得一个矩阵 ( i − U p + 1 , p o s ) − ( i , j ) (i-Up+1,pos)-(i,j) (i−Up+1,pos)−(i,j)这个矩阵不能够再向左、上、右方向拓展,所以只需要看看是否能够向下拓展即可判断该矩阵是否计入答案。记录每一行的前缀和,能否向下拓展就可以 O ( 1 ) O(1) O(1)判断了。
我的队友脑洞出来了一个解法,能将空间复杂度优化到 O ( n ) O(n) O(n)。首先,我们每读入进来一个0,如果它的上方是1,那么就可以确定上方有一个或多个全1矩阵已经无法向下扩展,那么我们只需要找到其中所以满足要求的矩阵个数加入答案,并且用某种技巧避免重复计数就可以解决这道题的。具体做法是这样的,每读入一行,我们仍然维护每一列从该行向上连续1的个数,记作 g r a [ i ] gra[i] gra[i],并且用 l [ i ] l[i] l[i]保存第 i i i列读取上一行时记录的 g r a [ i ] gra[i] gra[i],再从左往右遍历这一行,每次遇到0,就选取它上一行满足左右扩展最远的前提下上方扩展最远的全1矩阵,也就是向左右两个方向扩展时记录正整数 l [ j ] l[j] l[j]的最小值那么这个矩阵一定是满足条件的,可以加入答案。之后我们将这些 l [ j ] l[j] l[j]都减去这个最小值,再做同样的操作,直到 l [ i ] l[i] l[i]为0为止,这样就可以准确计数避免重复。
#include< bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e3+5;
inline int Min(int a,int b){return a< b?a:b;}
int n,m,gra[N],l[N];
ll ans;
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n+1;i++){
getchar();
for(int j=1;j<=m;j++){
l[j] = gra[j];
gra[j] = (i <= n && getchar()-'0')?gra[j]+1:0;
}
for(int j=1;j<=m;j++){
if(gra[j] == 0){
while(l[j]){
// printf("\n!%d %d %d\n",i,j,l[j]);
int first=j-1,last=j+1,mi=l[j];
while(first && l[first]){
mi = Min(mi,l[first]);
first--;
}
while(last <= m && l[last]){
mi = Min(l[last],mi);
last++;
}
ans++;
for(int k=first+1;k< last;k++){
l[k] -= mi;
}
// printf("!%d %d %d\n",first,last,mi);
}
}
}
}
printf("%lld\n",ans);
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/B
来源:牛客网
Gromah and LZR have entered the second level. There is a sequence a 1 , a 2 , ⋯ , a n a_1,a_2,⋯,a_n a1,a2,⋯,an on the wall.
There is also a note board saying “the beauty value of a sequence is the number of different elements in the sequence”.
LZR soon comes up with the password of this level, which is the sum of the beauty values of all successive subintervals of the sequence on the wall.
Please help them determine the password!
The first line contains one positive integer n n n, denoting the length of the sequence.
The second line contains n n n positive integers a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots, a_n a1,a2,⋯,an denoting the sequence.
1 ≤ a i ≤ n ≤ 1 0 5 1≤a_i≤n≤10^5 1≤ai≤n≤105
Print a non-negative integer in a single line, denoting the answer.
4
1 2 1 3
18
The beauty values of subintervals [ 1 , 1 ] , [ 2 , 2 ] , [ 3 , 3 ] , [ 4 , 4 ] [1,1],[2,2],[3,3],[4,4] [1,1],[2,2],[3,3],[4,4]are all 1.
The beauty values of subintervals [ 1 , 2 ] , [ 1 , 3 ] , [ 2 , 3 ] , [ 3 , 4 ] [1,2],[1,3],[2,3],[3,4] [1,2],[1,3],[2,3],[3,4]are all 2.
The beauty values of subintervals [ 1 , 4 ] , [ 2 , 4 ] [1,4],[2,4] [1,4],[2,4]are all 3.
As a result, the sum of all beauty values are 1 × 4 + 2 × 4 + 3 × 2 = 18 1×4+2×4+3×2=18 1×4+2×4+3×2=18.
题目大意是定义一个数列的值是这个数列中不同元素的个数,现在给出一个数列,求这个数列的所有子数列的值的和。
社区送温暖题,考虑计算每个元素对答案的贡献。对于一个数列来说,相同的元素我们只计第一个该元素对答案的贡献,那么记录每个元素上一次出现的位置,只要是包含这个元素且左端点在该元素上一次出现的位置的右边的区间,这一个元素都会对答案做一次贡献。直接计数就好了。
#include
using namespace std;
const int N=100005;
int lst[N],n;
long long ans;
int main(){
scanf("%d",&n);
for(int x,i=1;i<=n;i++){
scanf("%d",&x);
ans+=1LL*(i-lst[x])*(n-i+1);
lst[x]=i;
}
printf("%lld\n",ans);
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/C
来源:牛客网
Gromah and LZR have entered the third level. There is a blank grid of size m × m m\times m m×m, and above the grid is a word “CDMA”.
In CDMA Technology, a Technology about computer network, every network node should be appointed a unique binary sequence with a fixed and the same length. Moreover, if we regard 0 in the sequence as −1, and regard 1 as +1, then these sequences should satisfy that for any two different sequences s , t s,t s,t, the inner product of s , t s,t s,t should be exactly 0.
The inner product of two sequences s , t s,t s,t with the same length n n n equals to ∑ i = 1 n s i t i \sum_{i=1}^{n} s_it_i ∑i=1nsiti.
So, the key to the next level is to construct a grid of size m × m m×m m×m, whose elements are all −1 or 1 and for any two different rows, the inner product of them should be exactly 0.
In this problem, it is guaranteed that m is a positive integer power of 2 and there exists some solutions for input m. If there are multiple solutions, you may print any of them.
Only one positive integer m in one line.
m ∈ 2 k ∣ k = 1 , 2 , ⋯ , 10 m∈{2^k∣ k=1,2,⋯ ,10} m∈2k∣ k=1,2,⋯ ,10
Print m lines, each contains a sequence with length m, whose elements should be all −1 or 1 satisfying that for any two different rows, the inner product of them equals 0.
You may print multiple blank spaces between two numbers or at the end of each line, and you may print any number of blank characters at the end of whole output file.
2
1 1
1 -1
The inner product of the two rows is 1×(−1)+1×1=0.
题目大意是让你构造一个大小为 2 k ∗ 2 k 2^k*2^k 2k∗2k且只包含1和-1的方阵,使得任意两个行向量垂直。
本来也应该是社区送温暖题,但是我还是想了好久。构造方法其实挺简单的,假设对于输入k的答案是矩阵 A A A,那么k+1的答案就是
( A A A − A ) \left( \begin{matrix} A&A\\ A&-A \end{matrix} \right) (AAA−A)
之后就直接递推构造就好了。
链接:https://ac.nowcoder.com/acm/contest/888/D
来源:牛客网
Gromah and LZR have entered the fourth level. There is a blank cube with size n × m × h n×m×h n×m×hhanging on the wall.
Gromah soon finds a list beside the cube, there are q q q instructions in the list and each instruction is in one of the following two formats:
Manhattan Distance between two positions ( x 1 , y 1 , z 1 ) , ( x 2 , y 2 , z 2 ) (x1,y1,z1),(x2,y2,z2) (x1,y1,z1),(x2,y2,z2)is defined as ∣ x 1 − x 2 ∣ + ∣ y 1 − y 2 ∣ + ∣ z 1 − z 2 ∣ ∣x1−x2∣+∣y1−y2∣+∣z1−z2∣ ∣x1−x2∣+∣y1−y2∣+∣z1−z2∣.
LZR also finds a note board saying that the password of this level is the sequence made up of all results of the instructions in format 2.
Please help them get all results of the instructions in format 2.
The first line contains four positive integers n,m,h,qn,m,h,q_{}n,m,h,q, denoting the sizes in three dimensions of the cube and the number of instructions.
Following q q q lines each contains four positive integers o p , x , y , z op,x,y,z op,x,y,z, where o p = 1 op=1 op=1 means to add a tag on ( x , y , z ) (x,y,z) (x,y,z) while o p = 2 op=2 op=2 means to make a query on ( x , y , z ) (x,y,z) (x,y,z).
1 ≤ n × m × h , q ≤ 1 0 5 , 1 ≤ x ≤ n , 1 ≤ y ≤ m , 1 ≤ z ≤ h 1≤n×m×h,q≤10^{5},1≤x≤n,1≤y≤m,1≤z≤h 1≤n×m×h,q≤105,1≤x≤n,1≤y≤m,1≤z≤h
It is guaranteed that the first instruction is in format 1 and that no position will be tagged more than once.
For each instruction in format 2, output the answer in one line.
3 3 3 4
1 1 1 1
2 2 3 3
1 3 1 1
2 3 3 2
5
3
For the first query, there is only one tagged position ( 1 , 1 , 1 ) (1,1,1) (1,1,1)currently, so the answer is ∣ 1 − 2 ∣ + ∣ 1 − 3 ∣ + ∣ 1 − 3 ∣ = 5 ∣1−2∣+∣1−3∣+∣1−3∣=5 ∣1−2∣+∣1−3∣+∣1−3∣=5.
For the second query, ( 3 , 1 , 1 ) (3,1,1) (3,1,1) is the nearest tagged position, so the answer is ∣ 3 − 3 ∣ + ∣ 1 − 3 ∣ + ∣ 1 − 2 ∣ = 3 ∣3−3∣+∣1−3∣+∣1−2∣=3 ∣3−3∣+∣1−3∣+∣1−2∣=3.
题目大意是在三维空间中动态维护一种操作以及回答一种查询,操作是在三维空间中标记一个点,查询是询问对于给出的一个点,离它最近的标记的点距离它的曼哈顿距离是多少。
这题一眼kd-tree啊有木有?但是,可(bian)爱(tai)的出题人故意卡了kd-tree。于是,我辛辛苦苦地码完了一个kd-tree后一发T掉,我又费尽心机地在上面添上了一个替罪羊重构。还是不断地T掉。
当我看到题解的那一刻我就懵了,正解是定期重构,对于当前标记过的点,一遍bfs可以处理出每个点离它最近的标记的点的距离,没有bfs过的点就暴力比较,一段时间后重新bfs。我寻思这这范围不是长宽高都在 1 0 5 10^5 105范围内吗,这怎么bfs?我重新读了一遍题后发现是体积范围在 1 0 5 10^5 105内。恍然大悟。
之后我又浏览了几份其他过掉的代码,发现有些甚至没有定期重构,每次加入点就暴力bfs。WTF?!这种暴力都放过去了却卡掉我的kd-tree加替罪羊重构,扎铁了呀老锌QAQ。
这是我比赛时写的kd-tree加替罪羊重构
#include
using namespace std;
const double A = 0.8;
const int N=100005;
int read(){
int ret=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9')ch=='-'?f=-1:1,ch=getchar();
while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
void write(int a){
if(a<0){putchar('-');a=-a;}
if(a>=10)write(a/10);
putchar(a%10+'0');
}
int n,m,h,q,x,y,z,rt,cmp_d,ans,op,tmp[N],ned[N],cnt;
struct NODE{
int d[3],mx[3],mi[3],l,r,sz;
}t[100005];
void kd_update(int p){
t[p].sz=t[t[p].l].sz+1+t[t[p].r].sz;
if(t[p].l){
t[p].mx[0]=max(t[p].mx[0],t[t[p].l].mx[0]);
t[p].mx[1]=max(t[p].mx[1],t[t[p].l].mx[1]);
t[p].mx[2]=max(t[p].mx[2],t[t[p].l].mx[2]);
t[p].mi[0]=min(t[p].mi[0],t[t[p].l].mi[0]);
t[p].mi[1]=min(t[p].mi[1],t[t[p].l].mi[1]);
t[p].mi[2]=min(t[p].mi[2],t[t[p].l].mi[2]);
}
if(t[p].r){
t[p].mx[0]=max(t[p].mx[0],t[t[p].r].mx[0]);
t[p].mx[1]=max(t[p].mx[1],t[t[p].r].mx[1]);
t[p].mx[2]=max(t[p].mx[2],t[t[p].r].mx[2]);
t[p].mi[0]=min(t[p].mi[0],t[t[p].r].mi[0]);
t[p].mi[1]=min(t[p].mi[1],t[t[p].r].mi[1]);
t[p].mi[2]=min(t[p].mi[2],t[t[p].r].mi[2]);
}
}
bool cmp(int a,int b){return t[a].d[cmp_d]<t[b].d[cmp_d];}
int build(int l,int r,int D){
int mid=(l+r)>>1; cmp_d=D;
nth_element(ned+l+1,ned+mid+1,ned+r+1,cmp);
int x=ned[mid];
t[x].mx[0]=t[x].mi[0]=t[x].d[0];
t[x].mx[1]=t[x].mi[1]=t[x].d[1];
t[x].mx[2]=t[x].mi[2]=t[x].d[2];
t[x].l=(l^mid)?build(l,mid-1,(D+1)%3):0;
t[x].r=(r^mid)?build(mid+1,r,(D+1)%3):0;
kd_update(x);return x;
}
void dfs( int x ){if(x)ned[++cnt]=x,dfs(t[x].l),dfs(t[x].r);}
void kd_insert(int now){
int d=0,p=rt,dep=0;
while(1){
tmp[++dep]=p;
t[p].sz++;
t[p].mx[0]=max(t[p].mx[0],t[now].mx[0]);
t[p].mi[0]=min(t[p].mi[0],t[now].mi[0]);
t[p].mx[1]=max(t[p].mx[1],t[now].mx[1]);
t[p].mi[1]=min(t[p].mi[1],t[now].mi[1]);
t[p].mx[2]=max(t[p].mx[2],t[now].mx[2]);
t[p].mi[2]=min(t[p].mi[2],t[now].mi[2]);
if(t[now].d[d]<=t[p].d[d]){
if(!t[p].l){t[p].l=now;break;}
else p=t[p].l;
}
else{
if(!t[p].r){t[p].r=now;break;}
else p=t[p].r;
}
d=(d+1)%3;
}
tmp[++dep]=now;
if(dep<log(t[rt].sz)/log(1/A)) return;
while((double)t[t[now].l].sz<A*t[now].sz&&(double)t[t[now].r].sz<A*t[now].sz)now=tmp[--dep];
if(!now)return;
if(now==rt){
cnt=0;dfs(rt);
rt=build(1,cnt,0);
return;
}
int y=tmp[--dep];
cnt=0;dfs(now);
int k=build(1,cnt,dep%3);
if(t[y].l==now)t[y].l=k;else t[y].r=k;
}
int dis(int p,int px,int py,int pz){
int ret=0;
if(px<t[p].mi[0])ret+=t[p].mi[0]-px;
else if(px>t[p].mx[0])ret+=px-t[p].mx[0];
if(py<t[p].mi[1])ret+=t[p].mi[1]-py;
else if(py>t[p].mx[1])ret+=py-t[p].mx[1];
if(pz<t[p].mi[2])ret+=t[p].mi[2]-pz;
else if(pz>t[p].mx[2])ret+=pz-t[p].mx[2];
return ret;
}
int kd_ask(int p){
int dl=1e9,dr=1e9,d0=abs(t[p].d[0]-x)+abs(t[p].d[1]-y)+abs(t[p].d[2]-z);
ans=min(ans,d0);
if(t[p].l)dl=dis(t[p].l,x,y,z);
if(t[p].r)dr=dis(t[p].r,x,y,z);
if(dl<dr){if(dl<ans)kd_ask(t[p].l);if(dr<ans)kd_ask(t[p].r);}
else {if(dr<ans)kd_ask(t[p].r);if(dl<ans)kd_ask(t[p].l);}
return ans;
}
int main(){
n=read();m=read();h=read();q=read();
rt=1;
op=read();x=read();y=read();z=read();
int ct=1;
t[ct].mx[0]=t[ct].mi[0]=t[ct].d[0]=x;
t[ct].mx[1]=t[ct].mi[1]=t[ct].d[1]=y;
t[ct].mx[2]=t[ct].mi[2]=t[ct].d[2]=z;
t[ct].sz=1;
for(int i=2;i<=q;i++){
op=read();x=read();y=read();z=read();
if(op==1){
++ct;
t[ct].mx[0]=t[ct].mi[0]=t[ct].d[0]=x;
t[ct].mx[1]=t[ct].mi[1]=t[ct].d[1]=y;
t[ct].mx[2]=t[ct].mi[2]=t[ct].d[2]=z;
t[ct].sz=1;
kd_insert(ct);
}
else
{ans=1e9;write(kd_ask(rt));putchar('\n');}
}
return 0;
}
这是我按照题解的思路写的定期重构
#include
using namespace std;
const int N=100005,d[6][3]={{1,0,0},{0,1,0},{-1,0,0},{0,-1,0},{0,0,1},{0,0,-1}};
int n,m,h,q,K,dis[N];
struct Point{
int x,y,z;
Point(){}
Point(int x,int y,int z):x(x),y(y),z(z){}
};
vector<Point>v;
int encode(int x,int y,int z){
return (x-1)*m*h+(y-1)*h+z;
}
void bfs(){
queue<Point>q;
for(int i=0;i<(int)v.size();i++){
q.push(Point(v[i].x,v[i].y,v[i].z));
dis[encode(v[i].x,v[i].y,v[i].z)]=0;
}while(!q.empty()){
Point u=q.front(),nu;q.pop();
for(int i=0;i<6;i++){
nu=u;nu.x+=d[i][0];nu.y+=d[i][1];nu.z+=d[i][2];
if(nu.x&&nu.y&&nu.z&&nu.x<=n&&nu.y<=m&&nu.z<=h&&dis[encode(nu.x,nu.y,nu.z)]>dis[encode(u.x,u.y,u.z)]+1){
dis[encode(nu.x,nu.y,nu.z)]=dis[encode(u.x,u.y,u.z)]+1;
q.push(nu);
}
}
}
}
int main(){
scanf("%d%d%d%d",&n,&m,&h,&q);
K=3*sqrt(n*m*h);
for(int i=1;i<=n*m*h;i++)dis[i]=1e9;
for(int op,x,y,z,i=1;i<=q;i++){
scanf("%d%d%d%d",&op,&x,&y,&z);
if(op==1){
v.emplace_back(x,y,z);
if((int)v.size()==K){
bfs();v.clear();
}
}else{
int ans=dis[encode(x,y,z)];
for(int j=0;j<(int)v.size();j++)
ans=min(ans,abs(x-v[j].x)+abs(y-v[j].y)+abs(z-v[j].z));
printf("%d\n",ans);
}
}
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/E
来源:牛客网
Gromah and LZR have entered the fifth level. Unlike the first four levels, they should do some moves in this level.
There are n vertices and m bidirectional roads in this level, each road is in format ( u , v , l , r ) (u,v,l,r) (u,v,l,r), which means that vertex u and v are connected by this road, but the sizes of passers should be in interval [ l , r ] [l,r] [l,r]. Since passers with small size are likely to be attacked by other animals and passers with large size may be blocked by some narrow roads.
Moreover, vertex 1 is the starting point and vertex n is the destination. Gromah and LZR should go from vertex 1 to vertex n to enter the next level.
At the beginning of their exploration, they may drink a magic potion to set their sizes to a fixed positive integer. They want to know the number of positive integer sizes that make it possible for them to go from 1 to n n n.
Please help them to find the number of valid sizes.
The first line contains two positive integers n , m n,m n,m, denoting the number of vertices and roads.
Following m lines each contains four positive integers u , v , l , r u,v,l,r u,v,l,r, denoting a bidirectional road ( u , v , l , r ) (u,v,l,r) (u,v,l,r).
1 ≤ n , m ≤ 1 0 5 , 1 ≤ u < v ≤ n , 1 ≤ l ≤ r ≤ 1 0 9 1≤n,m≤10^5,1≤u<v≤n,1≤l≤r≤10^9 1≤n,m≤105,1≤u<v≤n,1≤l≤r≤109
Print a non-negative integer in a single line, denoting the number of valid sizes.
5 5
1 2 1 4
2 3 1 2
3 5 2 4
2 4 1 3
4 5 3 4
2
There are 2 valid sizes : 2 and 3.
For size 2, there exists a path 1→2→3→5.
For size 3, there exists a path 1→2→4→5.
题目大意是给出一个无向图,每条边有一个通过人数的上限和下限,一群人要一起从1号点走到n号点,这一群人一起走不能分开,问这群人的人数有多少种可以满足条件。
我一开始的思路是有上下界的网络流,因为这个模型实在是太像了。之后我发现这个问题好像并不容易用网络流来做,而且数据范围也不像是网络流能做的样子。但是又想不出别的方法,只好放弃。
这题的正解处理得非常巧妙。首先,把每条边通过人数的上下限离散化,建一个以通过人数size为关键字的线段树,相应节点保存覆盖该线段的所有边。从根开始对线段树dfs,到某个节点时把该节点保存的边添加到图当中,这一步用秩合并优化的并查集来处理,之后check一下1和n是否连通,如果连通就计入答案并回溯,否则继续往下走直到连通或者叶节点。因为回溯的时候要撤销并查集的合并操作,所以在添边的时候用秩合并优化的并查集,保存之前合并的点,这样就可以在回溯时撤销了。
#include
#define lson u<<1,l,mid
#define rson u<<1|1,mid+1,r
using namespace std;
const int N=100005;
int n,m,U[N],V[N],L[N],R[N],fa[N],sz[N],ans;
vector<int>v,e[N<<3];
int find(int x){return fa[x]==x?x:find(fa[x]);}
int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
void update(int u,int l,int r,int x,int y,int val){
if(x<=l&&r<=y){
e[u].push_back(val);
return;
}int mid=(l+r)>>1;
if(x<=mid)update(lson,x,y,val);
if(y>mid)update(rson,x,y,val);
}
void dfs(int u,int l,int r){
vector<int>lst;int mid=(l+r)>>1;
for(int i=0;i<(int)e[u].size();i++){
int x=U[e[u][i]],y=V[e[u][i]],fx=find(x),fy=find(y);
if(sz[fx]>sz[fy])fx^=fy^=fx^=fy;
lst.push_back(fx);fa[fx]=fy;sz[fy]+=sz[fx];
}if(find(1)==find(n))ans+=v[r]-v[l-1];
else if(l<r){dfs(u<<1,l,mid);dfs(u<<1|1,mid+1,r);}
for(int i=lst.size()-1;~i;i--)fa[lst[i]]=lst[i];
lst.clear();
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d%d%d%d",&U[i],&V[i],&L[i],&R[i]);
v.push_back(L[i]);v.push_back(R[i]+1);
}sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
for(int i=1;i<=n;i++){
fa[i]=i;sz[i]=1;
}
for(int i=1;i<=m;i++){
update(1,1,v.size(),getid(L[i]),getid(R[i]+1)-1,i);
}dfs(1,1,v.size());printf("%d\n",ans);
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/F
来源:牛客网
Gromah and LZR have entered the sixth level.
There are nn_{}n pairwise distinct points on the wall, each can be described as P i ( x i , y i ) ( 1 ≤ i ≤ n ) P_i(x_i, y_i) (1≤i≤n) Pi(xi,yi)(1≤i≤n)in a 2-dimensional rectangular coordinate system.
LZR finds a note board saying that four distinct points A , B , C , D A,B,C,D A,B,C,D form a flower if there exists a point among the four points strictly inside the triangle formed by the other three points, where the triangle should be non-degenerate.
So the password of this level is naturally the number of tuples ( i , j , k , l ) ( 1 ≤ i < j < k < l ≤ n ) (i,j,k,l)(1≤i<j<k<l≤n) (i,j,k,l)(1≤i<j<k<l≤n) that P i , P j , P k , P l P_i, P_j, P_k, P_l Pi,Pj,Pk,Pl form a flower.
Please help them to calculate the answer.
The first line contains one positive integer n n n, denoting the number of points.
Following n n n lines each contains two integers x i , y i x_i, y_i xi,yi, denoting a point.
4 ≤ n ≤ 1000 , ∣ x ∣ , ∣ y ∣ ≤ 1 0 9 4≤n≤1000,∣x∣,∣y∣≤10^9 4≤n≤1000,∣x∣,∣y∣≤109
n n n points are pairwise distinct.
Print a non-negative integer in a single line, denoting the number of flowers.
8
0 0
0 1
-1 0
0 -1
1 1
1 -1
-1 1
-1 -1
5
The 5 flowers are
{ ( 0 , 0 ) , ( − 1 , 1 ) , ( 1 , 1 ) , ( 0 , − 1 ) } , \{(0,0),(−1,1),(1,1),(0,−1)\}, {(0,0),(−1,1),(1,1),(0,−1)},
{ ( 0 , 0 ) , ( − 1 , − 1 ) , ( 1 , − 1 ) , ( 0 , 1 ) } , \{(0,0),(−1,−1),(1,−1),(0,1)\}, {(0,0),(−1,−1),(1,−1),(0,1)},
{ ( 0 , 0 ) , ( 1 , 1 ) , ( 1 , − 1 ) , ( − 1 , 0 ) } , \{(0,0),(1,1),(1,−1),(−1,0)\}, {(0,0),(1,1),(1,−1),(−1,0)},
{ ( 0 , 0 ) , ( 1 , 1 ) , ( 0 , − 1 ) , ( − 1 , 0 ) } , \{(0,0),(1,1),(0,−1),(−1,0)\}, {(0,0),(1,1),(0,−1),(−1,0)},
{ ( 0 , 0 ) , ( 0 , 1 ) , ( 1 , − 1 ) , ( − 1 , 0 ) } . \{(0,0),(0,1),(1,−1),(−1,0)\}. {(0,0),(0,1),(1,−1),(−1,0)}.
题目大意是定义平面上四个点形成一朵花为其中三个点形成一个三角形,并且第四个点严格包含于这个三角形。给出平面上 n n n个点,求出有多少朵花。
官方题解是这样说的:
•枚举点 A,对其他点进行极角排序,然后按照极角序枚举 B,不妨设 C 点必须满足 AB×AC < 0,即在 AB 的“右”侧,考虑维护其他每个满足以上条件的点作为 C 点的情况下,可取的 D 点的个数。
•考虑 B 点每往逆时针方向转一下所带来的影响。
•可以看到:每当有一个点从 C 点变成非 C 就会产生一次单点修改 和区间加一,所以可以用线段树维护每个点作为 C 点的可行 D 点 个数,每转移一次就统计一下答案即可。
其实我并没有怎么看懂。。。 于是我去阅读了一下别人的AC代码,有一种方法很巧妙,大致思路是先枚举每个点,把它作为三角形中间的那个点,其他的点如果任意选择,就有 C n − 1 3 C_{n-1}^{3} Cn−13种选法,之后再减去不合法的。不合法的计数方法是按极角顺序枚举从这个点到其他所有点的向量,用一个指针指向这个向量左边最后的点,这样就可以记录它左边一共有多少个点,这样如果算上向量的两个点,左边任意选择两个点,那么枚举的这个点肯定不可能作为中间点构造出一朵花,于是在答案中减去这一部分。要注意的是对于四点共线这种情况要注意不要减重了,如果直接按照前面的方法减则会多减去一部分,所以要考虑加回来。
#include
using namespace std;
const int N=1005;
typedef long long LL;
struct POINT{
LL x,y;int k;
POINT(){}
POINT(LL x,LL y){
this->x=x;this->y=y;int k;
if(x>0&&y>=0)k=1;
else if(x<=0&&y>0)k=2;
else if(x<0&&y<=0)k=3;
else k=4;
this->k=k;
}
POINT operator - (const POINT&b)const{
return POINT(x-b.x,y-b.y);
}LL operator * (const POINT&b)const{
return x*b.y-y*b.x;
}bool operator < (const POINT&b)const{
if(k^b.k)return k<b.k;
return (*this)*b>0;
}bool operator == (const POINT&b)const{
return (*this)*b==0&&x*b.x+y*b.y>0;
}
}p[N],vec[N];
int n,c;
LL ans;
LL solve(){
if(vec[0]==vec[c-1])return 0;
LL res=1LL*c*(c-1)*(c-2)/6;
for(int i=0,pos=0,cnt=0,lst=0,cc=0;i<c;i++){
if(!(vec[i]==vec[lst])){
lst=i;cc=0;
}while((pos+1)%c!=lst&&vec[i]*vec[(pos+1)%c]>=0){
pos=(pos+1)%c;
cnt++;
if(!(vec[i]==vec[pos])&&vec[i]*vec[pos]==0)cc++;
}res-=cnt*(cnt-1)/2;
res+=cc*(cc-1)/2;
if(cnt)cnt--;
else pos=(pos+1)%c;
}return res;
}
int main(){
scanf("%d",&n);
for(int x,y,i=0;i<n;i++){
scanf("%d%d",&x,&y);
p[i]=POINT(x,y);
}for(int i=0;i<n;i++){c=0;
for(int j=0;j<n;j++)if(i^j){
vec[c++]=p[j]-p[i];
}sort(vec,vec+c);
ans+=solve();
}
printf("%lld\n",ans);
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/G
来源:牛客网
Gromah and LZR have entered the seventh level. There are a sequence of gemstones on the wall.
After some tries, Gromah discovers that one can take exactly three successive gemstones with the same types away from the gemstone sequence each time, after taking away three gemstones, the left two parts of origin sequence will be merged to one sequence in origin order automatically.
For example, as for “ATCCCTTG”, we can take three 'C’s away with two parts “AT”, “TTG” left, then the two parts will be merged to “ATTTG”, and we can take three 'T’s next time.
The password of this level is the maximum possible times to take gemstones from origin sequence.
Please help them to determine the maximum times.
Only one line containing a string s s s, denoting the gemstone sequence, where the same letters are regarded as the same types.
1 ≤ ∣ s ∣ ≤ 1 0 5 1≤∣s∣≤10^5 1≤∣s∣≤105
s s s only contains uppercase letters.
Print a non-negative integer in a single line, denoting the maximum times.
ATCCCTTG
2
One possible way is that ‘‘ATCCCTTG " → ‘‘ATTTG” →‘‘AG”.
题目大意是给出一个字符串,如果每出现相邻三个字符相等,就可以把这三个字符消掉,剩下两段再拼接在一起,直到不能再操作为止。
本来是一道社区送温暖题,硬生生的被我做成了不那么温暖的题。正解是用一个栈就可以解决了。但是我傻乎乎的用了一个分治,写起来还挺麻烦的。
#include
using namespace std;
const int N=100005;
string s;
string solve(string str){
int l=str.length();
if(l<3)return str;
string s1,s2;
for(int i=0;i<l/2;i++)s1+=str[i];
for(int i=l/2;i<l;i++)s2+=str[i];
s1=solve(s1);
s2=solve(s2);
int l1=s1.length(),l2=s2.length(),p1=l1-1,p2=0;
while(p1>=0&&p2<l2){
if(p1>=1&&s1[p1]==s1[p1-1]&&s1[p1]==s2[p2]){
p1-=2;p2++;
}else if(p2<l2-1&&s2[p2]==s1[p1]&&s2[p2]==s2[p2+1]){
p1--;p2+=2;
}else break;
}
string res;
for(int i=0;i<=p1;i++)res+=s1[i];
for(int i=p2;i<l2;i++)res+=s2[i];
return res;
}
int main(){
cin>>s;
cout<<(s.length()-solve(s).length())/3<<endl;
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/H
来源:牛客网
Gromah and LZR have entered the eighth level. There is an n n n-vertex tree, acyclic bidirectional connected graph painted on the wall. The vertices are conveniently labeled with 1 , 2 , ⋯ , n 1,2,⋯ ,n 1,2,⋯ ,n.
LZR finds that for every edge e e e in the tree, there is a non-empty set of lowercase letters ses_ese above it. Meanwhile, Gromah finds m m m strings t 1 , t 2 , ⋯ , t m t_1,t_2,⋯ ,t_m t1,t2,⋯ ,tm and q q q queries ( u 1 , v 1 ) , ( u 2 , v 2 ) , ⋯   , ( u q , v q ) (u_1, v_1), (u_2, v_2), \cdots, (u_q, v_q) (u1,v1),(u2,v2),⋯,(uq,vq) beside the tree.
Soon, LZR finds a note board saying that for each query ( u , v ) (u,v) (u,v), you may choose exactly one letter from s e s_e se for all edges e e e in the chain u → v u \rightarrow v u→v and write down the chosen letters in the order of the directed chain u → v u→v u→v to form a string s t r str str , and the answer to this query is the number of schemes to choose letters so that the result string s t r str str completely contains at least one of the m m m given strings t 1 , t 2 , ⋯   , t m t_1, t_2, \cdots, t_m t1,t2,⋯,tm.
Moreover, the answers to queries may be very large, so they only need to know the answers modulo 998244353 998244353 998244353.
Please help them answer the queries to enter the next level.
The first line contains three positive integers n,m,qn,m,q_{}n,m,q, denoting the size of the tree, the number of given strings and the number of queries.
Following n − 1 n−1 n−1 lines each contains two positive integers u , v u,v u,v and a string s t r e str_e stre, denoting an edge between u u u and v v v with a set { c    ∣    c ∈ s t r e } \{c \; | \; c \in str_e\} {c∣c∈stre} above it.
Following m m m lines each contains a string t i t_i ti.
Following q q q lines each contains two positive integers u , v u,v u,v, denoting a query ( u , v ) (u,v) (u,v).
1 ≤ n ≤ 2500 , 1 ≤ q ≤ 5000 , 1 ≤ m , ∑ ∣ t i ∣ ≤ 40 1≤n≤2500,1≤q≤5000,1≤m,∑∣ti∣≤40 1≤n≤2500,1≤q≤5000,1≤m,∑∣ti∣≤40
The given graph forms a tree.
strestr_estre only contains pairwise distinct lowercase letters.
t i t_i tionly contains lowercase letters.
u = ̸ v u= \not v u≠v for all queries.
Output q q q lines, each contains one non-negative integer denoting the answer to corresponding query modulo 998244353 998244353 998244353.
5 2 3
1 2 nkei
1 3 ieu
2 4 nk
2 5 ne
niu
ke
3 4
4 3
3 5
0
6
3
For the first query, there is no valid scheme.
For the second query, the 6 result strings are n i u , n k e , k k e , k e i , k e e , k e u niu,nke,kke,kei,kee,keu niu,nke,kke,kei,kee,keu.
For the third query, the 3 result strings are i k e , e k e , u k e ike,eke,uke ike,eke,uke.
题目大意是给出一棵树,树上每条边有一个字母的集合,给出 m m m个字符串 t i t_i ti,有 q q q个询问 ( u , v ) (u,v) (u,v),表示从 u u u走到 v v v,每走一条边就在这条边上选择一个字母,走完过后得到一个字符串,要求这个字符串至少包含给出的 m m m个字符串中的一个,问题是符合条件的有多少种走法。
这题巨坑!!!!!
首先对于m个模板串建立一个ac自动机,然后用 f [ w ] [ x ] f[w][x] f[w][x]表示从u走到w,目前处于ac自动机上状态x下的方案数。转移的时候就是枚举当前边上的每一个字母,在ac自动机上转移到 f [ n e x t ( w ) ] [ n e x t ( x ) ] f[next(w)][next(x)] f[next(w)][next(x)]当中。由于有多组询问,我们使用矩阵来转移。对于每一条边,我们都可以构造一个转移矩阵,之后再用倍增维护转移矩阵的累乘结果。预处理复杂度为 O ( n t ∣ Σ ∣ + n t 3 l o g n ) O(nt|\Sigma|+nt^3logn) O(nt∣Σ∣+nt3logn),单次查询是 O ( t 2 l o g n ) O(t^2logn) O(t2logn)。但是据官方题解所说,由于预处理的复杂度太高,这种做法是会T掉的。
考虑一个优化,对于深度为x的点,记x的lowbit为 2 j 2^j 2j,那么只要预处理x往上跳 2 0 , 2 1 , 2 2 , ⋯   , 2 j 2^0,2^1,2^2,\cdots,2^j 20,21,22,⋯,2j这j-1个矩阵即可,询问的时候先求出lca,再用类似于树状数组的写法,从这个点依次网上跳,把转移矩阵累乘起来。但是这仍然是可以卡到 O ( n t 3 l o g n ) O(nt^3logn) O(nt3logn)的,只需要构造很多个深度为2的整数次幂的节点,那么这些点的lowbit就是它本身,所要预处理的部分和原来没有区别。
对于这一部分的处理,官方题解给出的方法是往根节点上添加若干个点。从二进制的最低位开始考虑,如果深度为偶数的点更多,那么在根节点上方再加一个点,保证深度为奇数的点不少于深度为偶数的点。同理去考虑其他二进制位。这样处理之后,只跳一次的点至少有 1 2 n \frac{1}{2}n 21n个,只跳两次的点至少是除去只跳一次的点后剩下的点的一半,那么 O ( t 3 ) O(t^3) O(t3)次的矩阵乘法要做的次数是 n 2 ∗ 1 + n 4 ∗ 2 + ⋯ < 2 n = O ( n ) \frac{n}{2}*1+\frac{n}{4}*2+\cdots<2n=O(n) 2n∗1+4n∗2+⋯<2n=O(n)这样预处理倍增的复杂度就降到了(nt^3)。
我觉得这样的处理实在是过于麻烦,于是就去看了看别人的ac代码,发现只要随机找一个点作为根就可以了。这样也不容易被卡掉,但是有时候还是会T掉,过不过主要还是看脸。
再来讲一些我学习的代码上处理的比较巧妙的地方,对于矩阵和向量,分别定义一个结构体,这样在矩阵乘以向量的时候只要 O ( n 2 ) O(n^2) O(n2)的复杂度,并且重载一下中括号的运算符,这样写起来比较方便。当然乘法也要重载。我还学到了两个内置函数fill_n和__builtin_ctz,第一个表示往某个数组里面填充某个值,从开头填充一定的次数,第二个函数表示把一个数转成二进制后末尾0的个数。
最后讲一讲很坑的地方。这题卡常,加法要用a+b>mod?a+b-mod:a+b来代替,直接(a+b)%mod会导致常数过大。还有就是我定义的每条边的字符串时设置的长度刚好是26,因为题目中说过保证没有相同字符出现。但是我在写循环的时候是这样写的:for(int i=0;s[i];i++),这样就会导致数组越界,所以我之后开到27后就能够过了。
#include
using namespace std;
const int N=2560,M=41,mod=998244353;
constexpr int add(int a, int b) { return a + b < mod ? a + b : a + b - mod; }
constexpr int sub(int a, int b) { return a < b ? a - b + mod : a - b; }
constexpr int mul(int a, int b) { return 1LL * a * b % mod; }
int head[N],cnt,ch[M][26],fail[M],ed[M],low[N],dep[N],n,m,q,tot,fa[N][12];
char str[N][27],s[M];
struct Edge{int v,next;}e[N<<1];
struct VEC{
int val[M];
VEC(){memset(val,0,sizeof(val));}
const int& operator[](int i)const{return val[i];}
int& operator[](int i){return val[i];}
int count(){int res=0;for(int i=0;i<M;i++)if(ed[i])res=add(res,val[i]);return res;}
};
struct matrix{
int val[M][M];
matrix(){memset(val,0,sizeof(val));}
const int* operator[](int i)const{return val[i];}
int* operator[](int i){return val[i];}
matrix operator*(const matrix&mat)const{
matrix ret;
for(int i=0;i<M;i++)for(int j=0;j<M;j++)for(int k=0;k<M;k++){
ret[i][j]=add(ret[i][j],mul(val[i][k],mat[k][j]));
}return ret;
}
VEC operator*(const VEC&vec)const{
VEC ret;
for(int i=0;i<M;i++)for(int j=0;j<M;j++){
//(ret[i]+=1LL*val[i][j]*vec[j]%mod)%=mod;
ret[i]=add(ret[i],mul(val[i][j],vec[j]));
}return ret;
}
}dp0[N][12],dp1[N][12],mat[N];
void adde(int u,int v){
e[++cnt]=(Edge){v,head[u]};
head[u]=cnt;
}
void insert(){
int u=0;
for(int i=0;s[i];i++){
int j=s[i]-'a';
if(!ch[u][j])ch[u][j]=++tot;
u=ch[u][j];
if(ed[u])break;
}ed[u]=1;
}
void build(){
queue<int>q;for(int i=0;i<26;i++)if(ch[0][i])q.push(ch[0][i]);
while(!q.empty()){
int u=q.front();q.pop();
if(ed[u]|=ed[fail[u]])fill_n(ch[u],26,u);
else{
for(int i=0;i<26;i++){
if(ch[u][i]){
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}else{
ch[u][i]=ch[fail[u]][i];
}
}
}
}
}
void dfs(int u,int f){
if(u^f){
fa[u][0]=f;
for(int i=1;i<12;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
low[u]=__builtin_ctz(dep[u]=dep[f]+1);
for(int i=1;i<=low[u];i++){
dp0[u][i]=dp0[u][i-1]*dp0[fa[u][i-1]][i-1];
dp1[u][i]=dp1[fa[u][i-1]][i-1]*dp1[u][i-1];
}
}else fill_n(fa[u],12,u);
for(int i=head[u];i;i=e[i].next)if(e[i].v^f){
dp0[e[i].v][0]=dp1[e[i].v][0]=mat[(i+1)>>1];
dfs(e[i].v,u);
}
}
int lca(int u,int v){
if(dep[u]<dep[v])swap(u,v);
for(int i=11;~i;i--)if(dep[fa[u][i]]>=dep[v])u=fa[u][i];
if(u==v)return u;
for(int i=11;~i;i--)if(fa[u][i]^fa[v][i])u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
int main(){
srand(time(nullptr));
scanf("%d%d%d",&n,&m,&q);
for(int u,v,i=1;i<n;i++){
scanf("%d%d%s",&u,&v,str[i]);
adde(u,v);adde(v,u);
}
for(int i=1;i<=m;i++){
scanf("%s",s);
insert();
}build();
for(int i=1;i<n;i++)for(int j=0;str[i][j];j++)for(int k=0;k<M;k++){
mat[i][ch[k][str[i][j]-'a']][k]++;
}
int r=rand()%n+1;
dfs(r,r);
for(int u,v,i=1;i<=q;i++){
scanf("%d%d",&u,&v);
VEC vec;vec[0]=1;
int f=lca(u,v),df=dep[f];
for(;dep[u]^df;)for(int i=low[u];~i;i--)if(dep[u]-(1<<i)>=dep[f]){//if(dep[fa[u][i]]>=df){
vec=dp1[u][i]*vec;
u=fa[u][i];
break;
}
vector<matrix *>tmp;
for(;dep[v]^df;)for(int i=low[v];~i;i--)if(dep[v]-(1<<i)>=dep[f]){//if(dep[fa[v][i]]>=df){
tmp.push_back(&dp0[v][i]);
v=fa[v][i];
break;
}
while(!tmp.empty()){
vec=*tmp.back()*vec;
tmp.pop_back();
}
printf("%d\n",vec.count());
}
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/I
来源:牛客网
Gromah and LZR are transfered to a forest, maybe it is the inner world of the great tomb. Initially, there are nn_{}n rooted trees numbered from 11_{}1 to nn_{}n with size 11_{}1 in the forest. For each tree, the only node is the root and labeled with 11_{}1.
After a while, here comes a farmer, and the farmer gives them mm_{}m planting tasks, each can be described by a tuple (u,v,l,r)(u,v,l,r){}(u,v,l,r), which means to add a labeled node vv{}v for all trees numbered from ll_{}l to rr_{}r, and their parent nodes are the nodes labeled with uu_{}u for each tree.
After finishing the planting tasks one by one, the farmer will give them qq_{}q querying tasks, each can be described by a tuple (x,l,r)(x,l,r){}(x,l,r), which means to query the sum of sizes of subtrees whose roots are the nodes labeled with xx{}x among the trees numbered from ll_{}l to rr_{}r. Specially, if there isn’t a node labeled with xx_{}x in a tree, the size of subtree xx_{}x is regarded as 00_{}0.
If they complete all tasks perfectly, the farmer will help them pass the final level.
Please help them handle these tasks.
The first line contains two positive integers n , m n,m n,m, denoting the number of trees in the inner world and the number of planting tasks.
Following m m m lines each contains four positive integers u , v , l , r u,v,l,r u,v,l,r, denoting a planting task ( u , v , l , r ) (u,v,l,r) (u,v,l,r).
The next line contains one positive integer q q q, denoting the number of querying tasks.
Following q q q lines each contains four positive integers x , l , r x,l,r x,l,r, denoting a querying task ( x , l , r ) (x,l,r) (x,l,r).
1 ≤ n , m , q ≤ 300000 , 1 ≤ u , x ≤ m + 1 , 2 ≤ v ≤ m + 1 , 1 ≤ l ≤ r ≤ n 1≤n,m,q≤300000,1≤u,x≤m+1,2≤v≤m+1,1≤l≤r≤n 1≤n,m,q≤300000,1≤u,x≤m+1,2≤v≤m+1,1≤l≤r≤n
For each planting task, It is guaranteed that the label v v v is unique among all v v vs, and the trees numbered from l l l to r r r all have a node labeled with u u u right before handling this task.
Output q q q lines, each contains one non-negative integer denoting the answer to corresponding query task.
4 3
1 2 1 2
1 3 2 4
3 4 2 3
2
1 1 4
3 1 4
11
5
Four trees are 1-2, 1(-2)-3-4, 1-3-4, 1-3.
In the four trees, sizes of subtrees 1 are 2,4,3,2, so the answer to the first query task is 2+4+3+2=11, while the sizes of subtrees 3 are 0,2,2,1 and the answer to the second query task is 0+2+2+1=5.
题目大意是,有n个树,一开始每棵树都只有一个根节点,根节点都为1号节点,之后进行m个操作,每个操作给出一组 ( u , v , l , r ) (u,v,l,r) (u,v,l,r)表示从第 l l l棵树到第 r r r棵树,每棵树都在节点u的下方添加一个儿子节点 v v v连接到 u u u。保证所有的 v v v各不相同。操作结束后有q个询问 ( x , l , r ) (x,l,r) (x,l,r),表示从第 l l l棵树到第 r r r棵树,每棵树的x节点子树大小总和为多少。
由于题目保证了所有树的 v v v各不相同,那么我们可以把所有树全部合并起来,而对于每一个节点保存一下它原本存在的区间是多少,这样处理之后,对于每个询问,实际上就是x的子树下把处于区间 [ l , r ] [l,r] [l,r]的部分全部求和。这样的话我们就可以以区间为关键字来建立线段树,按照dfs序来遍历,遍历到每个点的时候,就在那个点对应的区间内加1。每一个询问则是求进入x和离开x之间这个时间段里区间 [ l , r ] [l,r] [l,r]所增加的值的总和。所以我们把询问离线了,在进入x之前对于该询问减去区间 [ l , r ] [l,r] [l,r]的值,离开x的时候再加回来。那么所得到的值就是这一段时间的变化量,也就是所要求的答案。因为操作只涉及到区间加和区间求和,我们可以使用常数更小的树状数组来代替线段树。
#include
using namespace std;
const int N=300010;
typedef long long LL;
int n,m,q,head[N],cnt,l[N],r[N],in[N],out[N],tot,id[N];
struct Edge{int v,next;}e[N];
struct NODE{int l,r,val,id;};
vector<NODE>G[N];
LL c1[N],c2[N],ans[N];
void add(LL *c,int x,LL val){for(;x<=tot;x+=x&-x)c[x]+=val;}
LL sum(LL *c,int x){
LL res=0;
for(;x;x-=x&-x)res+=c[x];
return res;
}
void update(int l,int r,LL val){
add(c1,l,val);add(c1,r+1,-val);
add(c2,l,val*l);add(c2,r+1,-val*(r+1));
}
LL query(int l,int r){
return ((r+1)*sum(c1,r)-sum(c2,r))-(l*sum(c1,l-1)-sum(c2,l-1));
}
void adde(int u,int v){
e[++cnt]=(Edge){v,head[u]};
head[u]=cnt;
}
void dfs(int u){
id[in[u]=++tot]=u;
for(int i=head[u];i;i=e[i].next)dfs(e[i].v);
out[u]=tot;
}
int main(){
scanf("%d%d",&n,&m);
for(int u,v,i=1;i<=m;i++){
scanf("%d%d",&u,&v);scanf("%d%d",&l[v],&r[v]);
adde(u,v);
}dfs(1);l[1]=1;r[1]=n;
scanf("%d",&q);
for(int x,l,r,i=1;i<=q;i++){
scanf("%d%d%d",&x,&l,&r);
G[in[x]-1].push_back((NODE){l,r,-1,i});
G[out[x]].push_back((NODE){l,r,1,i});
}
for(int x,i=1;i<=tot;i++){
x=id[i];
update(l[x],r[x],1);
for(int j=0;j<(int)G[i].size();j++){
ans[G[i][j].id]+=1LL*G[i][j].val*query(G[i][j].l,G[i][j].r);
}
}
for(int i=1;i<=q;i++)printf("%lld\n",ans[i]);
return 0;
}
链接:https://ac.nowcoder.com/acm/contest/888/J
来源:牛客网
Gromah and LZR have entered the final level. In this level, they need to cross the sea of death to gain the treasures.
The sea of death is of width L L L, and there are L − 1 L−1 L−1 stones inside the sea. Assume that Gromah and LZR are at position 0 0 0 initially, that the treasures are at position $L, and that stones are at position 1 , 2 , ⋯ , L − 1 1,2,⋯ ,L−1 1,2,⋯ ,L−1 respectively. So Gromah and LZR can jump on the stones to cross the sea.
Unfortunately, the stones are not stable. To ensure safety, Gromah and LZR should go forward at least d d d positions each time. Formally, if they are at position x x x currently, they should jump onto the positions not less than x + d x+d x+d. Moreover, they can’t be at positions greater than L L L in any time, which means that the destination of their last jump should be exactly position L L L, or the treasure keepers will see them and come to eat them.
More unfortunately, the Infernos under the sea will attack them m m m times in total, each attack can be described as a tuple ( t , p ) (t,p) (t,p), denoting that the stone at position p p p will be attacked right after their t t t-th jump, which means their destination of t t t-th jump can’t be position p p p.
But fortunately, here comes the farmer(mentioned in problem I but not necessary to care in this problem) and he tells Gromah and LZR all the attack plans, so they can work out some jumping plans according to the information given by the farmer to get to the destination without being attacked.
Please help them determine the number of jumping plans satisfying all the restrictions described above. Since the number may be very large, you should report it modulo 998244353 998244353 998244353.
Two jumping plans are considered different if there exists at least one t t t that their positions after t t t-th jump are different in two plans.
The first line contains three positive integers L , d , m L,d,m L,d,m, denoting the width of the sea, the lower bound of jumping distance, and the number of attacks.
Following mm_{}m lines each contains two positive integers t,pt,p_{}t,p, denoting an attack ( t , p ) (t,p) (t,p).
1 ≤ d ≤ L ≤ 1 0 7 , 1 ≤ t , p < L , 1 ≤ m ≤ 3000 1≤d≤L≤10^7,1≤t,p<L,1≤m≤3000 1≤d≤L≤107,1≤t,p<L,1≤m≤3000
m m m attacks are pairwise distinct, where two attacks ( t 1 , p 1 ) , ( t 2 , p 2 ) (t_1, p_1), (t_2, p_2) (t1,p1),(t2,p2) are considered different if t 1 = ̸ t 2 t_1=\not t_2 t1≠t2 or p 1 = ̸ p 2 p_1 =\not p_2 p1≠p2 or both.
Print a non-negative integer in one line, denoting the answer modulo 998244353 998244353 998244353.
5 2 1
1 2
2
There are three plans originally : 0 → 2 → 5 , 0 → 3 → 5 , 0 → 5 0→2→5, 0→3→5, 0→5 0→2→5, 0→3→5, 0→5. But after the first jump, the stone at position 2 will be attacked, so plan 0 → 2 → 5 0→2→5 0→2→5 should be abandoned and 2 valid plans remain.
题目大意是河面上又L-1个石头,你要从河岸这一边跳到另一边,每次跳的时候至少要跨d个石头,现在告诉你多个,石头受到攻击的信息 ( t i , p i ) (t_i,p_i) (ti,pi),表示在 t i t_i ti的时候 p i p_i pi位置的石头会受到攻击,所以这个时候你不能够在这块石头上。问题是跳过河岸有多少种方案。
先不考虑受到攻击的情况,这显然就是个dp嘛。 d p [ i ] dp[i] dp[i]就表示到达i的方案数。转移方程也很简单,就是 d p [ i ] = d p [ 0 ] + d p [ 1 ] + d p [ 2 ] + ⋯ + d p [ i − d ] dp[i]=dp[0]+dp[1]+dp[2]+\cdots+dp[i-d] dp[i]=dp[0]+dp[1]+dp[2]+⋯+dp[i−d],用前缀和可以 O ( 1 ) O(1) O(1)转移。现在考虑攻击的情况。我们所求的答案就是 d p [ L ] dp[L] dp[L]减去受到攻击的方案数。考虑受到第i个攻击,那么方案数就是在 t i t_i ti时到达 p i p_i pi的情况乘以从 p i p_i pi到L的情况,也就是 d p [ L − p i ] dp[L-p_i] dp[L−pi]。之后就是容斥,减掉受到一次攻击的情况,再把受到两次攻击的情况加回来,再减去三次的情况。。。也就是把奇数次攻击的情况减掉再把偶数次攻击的情况加回来。我们先把攻击按照位置或者时间排序,那么如果经历过攻击k,这之后就不会再经历k之前的攻击了,那么就可以记 g [ u ] [ 0 / 1 ] g[u][0/1] g[u][0/1]来表示处于排好序的第u次攻击状态下前面一共经历了偶数/奇数次攻击的情况总数, O ( n 2 ) O(n^2) O(n2)暴力转移即可,从第j次攻击转移到第i次攻击的方案有 C d p o s − d t ∗ t + d t − 1 d t − 1 C_{dpos-dt*t+dt-1}^{dt-1} Cdpos−dt∗t+dt−1dt−1种,这个用插板法可以推导出来。
#include
using namespace std;
const int N=1e7+5,M=3005,mod=998244353;
typedef long long LL;
typedef pair<int,int> pii;
LL fac[N],g[M][2],rfac[N],dp[N];
pii att[N];
int L,d,m;
LL pw(LL a,LL b){
LL res=1;
for(;b;b>>=1,(a*=a)%=mod)if(b&1)(res*=a)%=mod;
return res;
}
LL C(LL n,LL m){
return (fac[n]*rfac[m])%mod*rfac[n-m]%mod;
}
bool cmp(pii a,pii b){
return a.second<b.second;
}
int main(){
cin>>L>>d>>m;
for(int i=1;i<=m;i++){
cin>>att[i].second>>att[i].first;
}sort(att+1,att+1+m,cmp);
fac[0]=rfac[0]=1;
for(int i=1;i<=L;i++)fac[i]=fac[i-1]*i%mod;
rfac[L]=pw(fac[L],mod-2);
for(int i=L-1;i>0;i--)rfac[i]=rfac[i+1]*(i+1)%mod;
dp[0]=1;
for(int i=1;i<=L;i++){
dp[i]=i<d?0:dp[i-d];
(dp[i]+=dp[i-1])%=mod;
}
LL ans=(dp[L]-dp[L-1]+mod)%mod;
g[0][0]=1;
for(int i=1;i<=m;i++){
for(int j=0;j<i;j++){
LL dpos=att[i].first-att[j].first,dt=att[i].second-att[j].second;
if(dpos<dt*d||dt<=0)continue;
LL a=dt-1,b=dpos-dt*d+dt-1,tmp=C(b,a);
(g[i][0]+=g[j][1]*tmp%mod)%=mod;
(g[i][1]+=g[j][0]*tmp%mod)%=mod;
}ans=(ans+(g[i][0]-g[i][1]+mod)%mod*(dp[L-att[i].first]-dp[L-att[i].first-1]+mod)%mod)%mod;
}
cout<<ans<<endl;
return 0;
}