week1的安排的专题是数据结构
题目集中大概包含这些数据结构&&算法:优先队列、栈、树状数组(一维+二维)、线段树、LCA(tarjin)、最小生成树、floyd、贪心、RMQ(ST表)、tire树(▶AC自动机),树直径
顺一遍这一周的一些数据结构
1.树状数组
复杂度:修改和查询的复杂度都是O(logN)
关于树状数组最经典的图当然就是…
几个函数模板:
int lowbit(x)
{
return x & -x;
}
//求前缀和
int getsum(int x)
{
int h=0;
while(x>=1)
{
h += c[x];
x -= lowbit(x);
}
return h;
}
void update(int x)
{
while(x<=M)
{
c[x]++;
x+=lowbit(x);
}
}
2.线段树
本篇不赘述线段树是什么了,就是一颗二叉搜索树…
复杂度:对于一维线段树来说,每次更新以及查询的时间复杂度为O(logN)。
复习一下本蒟蒻常用的函数模板:
建树:
void build(long long p,long long l,long long r)
{
t[p].l = l;
t[p].r = r;
long long ls = p<<1,rs = p<<1|1;
long long mid = (l+r)>>1;
if( l!=r )
{
build( ls , l , mid );
build( rs , mid+1 , r );
t[p].val = t[ ls ].val + t[ rs ].val;
}
else
{
t[p].val = a[l];
}
}
带lazy_tag的区间更新(单点更新可以看成区间更新):
void pushdown(long long p)
{
long long ls = p<<1,rs = p<<1|1;
if( add[p] && ( t[p].l!=t[p].r ) )
{
add[ls] += add[p];
add[rs] += add[p];
t[ls].val += add[p]*( t[ls].r-t[ls].l+1 );
t[rs].val += add[p]*( t[rs].r-t[rs].l+1 );
add[p] = 0;
}
}
void update(long long p,long long l,long long r,long long va)
{
pushdown(p);
if( t[p].l==l && t[p].r==r )
{
add[p] += va;
t[p].val += va*( r-l+1 );
return ;
}
if( t[p].l==t[p].r ) return ;
long long mid = (t[p].l+t[p].r)>>1;
if( r<=mid ) update( p<<1 , l , r , va );
else if(l>mid) update( p<<1|1 , l , r , va );
else
{
update( p<<1 , l , mid , va );
update( p<<1|1 , mid+1 , r , va );
}
t[p].val = t[p<<1].val + t[p<<1|1].val;
}
带lazy_tag区间求和:
long long query(long long p,long long l,long long r)
{
pushdown(p);
long long ls = p<<1;
long long rs = p<<1|1;
if( t[p].l==l&&t[p].r==r ) return t[p].val;
long long mid = (t[p].l+t[p].r)>>1;
if(r<=mid) return query(ls,l,r);
else if(l>mid) return query(rs,l,r);
else return ( query(ls,l,mid) + query(rs,mid+1,r) );
}
其他的建树方式
本周在帮ACM队中XYR学长改某道题的时候(题集TB),发现了一种之前基本没见过的建树方法,提前先把整棵树的深度确定,然后在更新的时候顺带把每个元素赋给叶子结点在从下往上更新最值,大致是:
void update(int k,int x)//更新操作,构造线段树
{
k += n-1;
cowMax[k]=x;
cowMin[k]=x;
val[k]++;
while(k)
{
k>>=1;
val[k] = val[k<<1] + val[k<<1|1];
cowMax[k]=max( cowMax[2*k] , cowMax[2*k+1] );//存区间最大值
cowMin[k]=min( cowMin[2*k] , cowMin[2*k+1] );//存区间最小值
}
}
int mian()
{
cin >>N>>Q;
while(n<N)
{
n*=2;
}
for(int i=1; i<=N; i++)
{
int x;
scanf("%d",&x);
update(i,x);
}
}
3.STL的优先队列
一些基本操作:
empty(); 队列为空返回1
pop(); 出队
push(); 入队
top(); 返回队列中优先级最高的元素
size(); 返回队列中元素的个数
关于优先队列中重载运算符:
struct point{
int x;
int y;
int times;
friend bool operator < (point a, point b)
{
return a.times > b.times; //重载小于号使得小的先出队列
}
};
4.tire树
一般来说,tire树拿来解决这样的问题:给出n个单词和m个询问,每次询问一个前缀,回答询问是多少个单词的前缀。依据tire树的性质,直接由字典树的根节点往下找就好了。
常用函数:
插入一个字符串:
inline void insert()
{
int p = 0,len = strlen(s);
for(int i=0;i<len;i++)
{
int c = s[i] - 'a';
if( t[p].son[c] == 0 )
{
sum++;
t[p].son[c] = sum;
t[ t[p].son[c] ].val = 1;//储存共有多少个字符串共同使用这个位置的这个字符
}
else t[ t[p].son[c] ].val++;
p = t[p].son[c];
}
t[p].tag = -1;//标记一下是否是一个字符串的最后一个字符
}
寻找前缀函数:
inline void search()
{
int p = 0,len = strlen(s);
for(int i=0;i<len;i++)
{
int c = s[i] - 'a';
if( t[p].son[c]==0 )
{
ans=0;
return ;
}
p = t[p].son[c];
}
ans = t[p].val;
}
5.ST表
ST表类似树状数组,线段树这两种算法,是一种用于解决RMQ(区间最值)问题的离线算法
与线段树相比,预处理复杂度同为O(nlogn),查询时间上,ST表为O(1),线段树为O(logn)
st表的主体是一个二维数组st[i][j],表示需要查询的数组的从下标i到下标i+2^j - 1的最值
预处理st表:
void getst()
{
int i,j;
for(i=0;i<n;i++)
{
m[i][0] = 1;
}
for(j=1;(1<<j)<=n;j++)
{
for(i=0;i+(1<<j)-1<n;i++)
{
if( m[i][j-1] > m[i+(1<<(j-1))][j-1] )
{
m[i][j] = m[i][j-1];
}
else m[i][j] = m[i+(1<<(j-1))][j-1];
//下面是需要处理两个区间中间连接部分的代码片
// int tmp = a[i+(1<<(j-1))-1],x = i+(1<<(j-1))-1,y=x,r = i+(1<<j)-1;
// while( x>=i&&a[x]==tmp ) x--;
// while( y<=r&&a[y]==tmp ) y++;
// int len = y-x-1;
// if( len>m[i][j] ) m[i][j] = len;
}
}
}
查找[ l , r ] 的区间最值:
int query(int l,int r)
{
int k=(int)(log((r-l+1)*1.0)/(log(2.0)));
int ans;
if( m[l][k] < m[r-(1<<k)+1][k] ) ans = m[r-(1<<k)+1][k];
else ans = m[l][k];
//下面是需要处理两个区间中间连接部分的代码片
// int tmp = a[l+(1<<k)-1],x = l+(1<<k)-1,y = x;
// while( x>=l&&a[x]==tmp ) x--;
// while( y<=r&&a[y]==tmp ) y++;
// int len = y-x-1;
// if( len > ans ) ans = len;
return ans;
}
.
.
.
下面是本周VJ上的题目集(各题题目就是题目入口):
T A - Fence Repair
(贪心+优先队列)
题意:题目大概是讲一块木板分成N块(L1…LN),每次切割的花费为被切割木板的长度,求总最小花费。
idea:正向来看的话可能会觉得每次都先割掉最大的一块会比较好,但是这样贪心是有问题的,全局达不到最优,可以考虑正难则反,转化成合并果子那题,反着来看,每次都选最短的两段合并,最后总的结果就是答案。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,a[51000],x,y;
long long ans=0;
priority_queue<int ,vector<int>, greater<int> >q;
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
q.push(a[i]);
}
while(q.size()>=2)
{
x = q.top();
q.pop();
y = q.top();
q.pop();
ans += (x+y);
q.push(x+y);
}
printf("%lld\n",ans);
return 0;
}
T B - Balanced Lineup
题意:给出n个数,m个操作,每次操作求[ l , r ]内的最大值和最小值的差。
idea:线段树求区间最值模板题(貌似ST表也可以)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define N 100000
using namespace std;
int n,m,a[N],l,r;
struct node
{
int l,r,maxx,minn;
}t[4*N];
void build(int p,int l,int r)
{
t[p].l = l;
t[p].r = r;
int mid = (l+r)>>1;
int ls = p<<1;
int rs = p<<1|1;
if(l!=r)
{
build(ls,l,mid);
build(rs,mid+1,r);
t[p].maxx = max( t[ls].maxx , t[rs].maxx );
t[p].minn = min( t[ls].minn , t[rs].minn );
}
else
{
t[p].maxx = a[l];
t[p].minn = a[l];
}
}
int findmax(int p,int l,int r)
{
int mid = (t[p].l+t[p].r)>>1,ls = p<<1,rs = p<<1|1;
if( t[p].l==l && t[p].r==r ) return t[p].maxx;
if(r<=mid) return findmax(ls,l,r);
else if(l>mid) return findmax(rs,l,r);
else
{
return max( findmax(ls,l,mid) , findmax(rs,mid+1,r) );
}
}
int findmin(int p,int l,int r)
{
int mid = (t[p].l+t[p].r)>>1,ls=p<<1,rs=p<<1|1;
if( t[p].l==l&&t[p].r==r ) return t[p].minn;
if(r<=mid) return findmin(ls,l,r);
else if(l>mid) return findmin(rs,l,r);
else
{
return min( findmin(ls,l,mid) , findmin(rs,mid+1,r) );
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
build(1,1,n);
for(int i=1;i<=m;i++)
{
scanf("%d %d",&l,&r);
cout<<findmax(1,l,r)-findmin(1,l,r)<<endl;
}
return 0;
}
T C - A Bug’s Life
题意:对于每一组数据,有n只虫子和m条交配纪录,让你给出是否有同性之间交配的行为(手动%哲学)。
idea:这题要用到种类并查集,一篇简介的种类并查集的解析
种类并查集关键就是利用“异性的异性就是同性”。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 2100
using namespace std;
int p,n,m,x,y;
int a,b,fa[2*N],sum=0;
int find(int i)
{
if( fa[i]==i ) return i;
else return fa[i] = find( fa[i] );
}
int main()
{
cin>>p;
while(p--)
{
sum++;
bool pd = true;
scanf("%d %d",&n,&m);
printf("Scenario #%d:\n",sum);
for(int i=1;i<=2*n;i++) fa[i] = i;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&a,&b);
x = find(a);
y = find(b);
if(x==y&&pd==true)
{
printf("Suspicious bugs found!\n");
pd = false;
}
fa[x] = find(b+n);
fa[y] = find(a+n);
}
if(pd) printf("No suspicious bugs found!\n");
cout<<endl;
}
return 0;
}
D - Nearest Common Ancestors
题意:给一棵树,求俩点的LCA
idea:tarjin模板
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const int N = 10001;
struct node
{
int from,to,val,next;
}e[N];
int t,n,m,head[N],sum = 0,x,y,ans,fa[N];
bool pd[N];
void add(int u,int v)
{
sum++;
e[sum].from = u;
e[sum].to = v;
e[sum].next = head[u];
head[u] = sum;
}
int find(int x)
{
if( fa[x]==x ) return x;
else return fa[x] = find( fa[x] );
}
void tarjin(int u)
{
pd[u] = true;
for(int i=head[u];i;i=e[i].next)
{
int v = e[i].to;
if( pd[v]==false )
{
tarjin(v);
fa[v] = u;
}
}
if( pd[x]==true&&pd[y]==true )
{
if(u==x)
{
ans = find(y);
}
else if(u==y)
{
ans = find(x);
}
return ;
}
}
int main()
{
// freopen("in.txt","r",stdin);
cin>>t;
while(t)
{
int u,v;
memset(head,0,sizeof(head));
sum = 0;
memset(pd,false,sizeof(pd));
cin>>n;
for(int i=1;i<=n;i++) fa[i] = i;
for(int i=1;i<n;i++)
{
scanf("%d %d",&u,&v);
fa[v] = find(u);
add(u,v);
}
cin>>x>>y;
int s = find(1);
for(int i=1;i<=n;i++) fa[i] = i;
tarjin(s);
cout<<ans<<endl;
t--;
}
return 0;
}
E - Minimum Inversion Number
题意:不断把第一个元素放到最后面,看看哪种情况下逆序对数量最小,输出。
idea:可以先暴力求出原始的逆序对数目,每当一个元素放到最后面时,逆序对的数目该变量是有公式的,比如当整个数组是{3,2,1,10,9,8,7,6,5,4},现在大小排第三的‘3’要放到最后一位了,变成{2,1,10,9,8,7,6,5,4,3},这个时候,总的逆序对数量应该是
原来逆序对数 + ( 10-3 -1) - ( 3-1 )
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
int n,a[10000],tot=0,minn=0x7ffffff;
int main()
{
memset(a,0,sizeof(a));
while(scanf("%d",&n)!=EOF)
{
tot = 0;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if( a[j] < a[i] ) tot++;
}
}
minn = tot;
for(int i=1;i<=n;i++)
{
tot += n-2*a[i]-1;
if(tot<minn)
{
minn = tot;
}
}
cout<<minn<<endl;
}
return 0;
}
F - Roads in the North
题意:求树上最远的两个点的距离,就是求树的直径
idea:两遍dfs即可求出树的直径。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 201000
using namespace std;
struct node
{
int from,to,val,next;
}e[M*2];
int n=1,m,sum = 0,head[M],dis[M];
bool pd[M];
inline void add(int u,int v,int val)
{
sum++;
e[sum].from = u;
e[sum].to = v;
e[sum].val = val;
e[sum].next = head[u];
head[u] = sum;
}
inline void dfs(int u)
{
pd[u] = true;
for(int i=head[u];i;i=e[i].next)
{
int v = e[i].to,val = e[i].val;
if( pd[v]==false )
{
dis[v] = dis[u] + val;
dfs(v);
}
}
}
int main()
{
int u,v,val;
memset(head,0,sizeof(head));
memset(pd,false,sizeof(pd));
memset(dis,0,sizeof(dis));
while( scanf("%d %d %d",&u,&v,&val)!=EOF )
{
// scanf("%d %d %d",&u,&v,&val);
add(u,v,val);
add(v,u,val);
n++;
}
dfs(1);
int ans = -1,maxx = -1,q;
for(int i=1;i<=n;i++)
{
if( dis[i]>=maxx )
{
q = i;
maxx = dis[i];
}
dis[i] = 0;
pd[i] = 0;
}
dfs(q);
for(int i=1;i<=n;i++)
{
ans = max( ans,dis[i] );
}
cout<<ans;
return 0;
}
G - 统计难题
idea:tire树模板题
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1001000
using namespace std;
char s[100000];
struct node
{
int son[26],tag,val;
node()
{
memset(son,0,sizeof(son));
val = 0;
}
}t[N];
int sum = 0,ans = 0;
inline void insert()
{
int p = 0,len = strlen(s);
for(int i=0;i<len;i++)
{
int c = s[i] - 'a';
if( t[p].son[c] == 0 )
{
sum++;
t[p].son[c] = sum;
t[ t[p].son[c] ].val = 1;
}
else t[ t[p].son[c] ].val++;
p = t[p].son[c];
}
t[p].tag = -1;
}
inline void search()
{
int p = 0,len = strlen(s);
for(int i=0;i<len;i++)
{
int c = s[i] - 'a';
if( t[p].son[c]==0 )
{
ans=0;
return ;
}
p = t[p].son[c];
}
ans = t[p].val;
}
int main()
{
while( gets(s) )
{
if( s[0] == NULL ) break;
insert();
}
while( gets(s) )
{
ans = 0;
search();
printf("%d\n",ans);
}
; return 0;
}
H - Xor Sum
idea:先手推找找规律。把每个数都补全为用32位二进制数,用二进制数创立tire树,对于询问的数,每一位二进制查找有没有其取反的结果,跳到其在字典树上取反的结果对应的子节点,没有就直接跳子节点。
I - Constructing Roads
idea:最小生成树模板题
ps:在处理两个点之间直接相连时处理成边权为0即可
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define N 110
using namespace std;
bool pd[N][N];
int sum=0,n,m,fa[N],ans=0,h=0;
struct node
{
int from,to,next,val;
}e[N*N];
inline void add(int u,int v,int val)
{
sum++;
e[sum].from = u;
e[sum].to = v;
e[sum].val = val;
}
inline bool cmp(node x,node y)
{
return x.val<y.val;
}
inline int find(int x)
{
if( fa[x]==x ) return x;
else return fa[x] = find( fa[x] );
}
void kruskal()
{
for(int i=1;i<=sum;i++)
{
int u = e[i].from,v = e[i].to,val = e[i].val;
int x = find(u),y = find(v);
if( x!=y )
{
fa[x] = y;
h++;
ans += val;
}
if( h==n-1 ) return ;
}
}
int main()
{
memset(pd,false,sizeof(pd));
int val,u,v,x,y;
cin>>n;
for(int i=1;i<=n;i++)
{
fa[i] = i;
for(int j=1;j<=n;j++)
{
scanf("%d",&val);
if( i!=j ) add(i,j,val);
}
}
cin>>m;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&u,&v);
add(u,v,0);
}
sort(e+1,e+sum+1,cmp);
kruskal();
cout<<ans;
return 0;
}
J - Stockbroker Grapevine
题意:n个人,每个人联系他所能联系到几个人所消耗的时间都不太一样,找从谁开始总时间最短。
idea:多源最短路问题,先用floyd 求出以任意一个点作为起点最小距离,找出从这个起点出发到其他点最大距离中最小的那一个。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 200
#define MAX 0x3f3f3f3f
using namespace std;
int n,m;
int dis[N][N],a[N],u;
void first()
{
for(int i=1;i<=n;i++)
{
dis[i][i] = 0;
for(int j=i+1;j<=n;j++)
{
dis[i][j] = MAX;
dis[j][i] = MAX;
}
}
}
void floyd()
{
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if( dis[i][j] > dis[i][k]+dis[k][j] )
{
dis[i][j] = dis[i][k]+dis[k][j];
}
}
}
}
}
int main()
{
int v,val;
while( scanf("%d",&n) )
{
if(n==0) break;
first();
for(int i=1;i<=n;i++)
{
scanf("%d",&m);
while(m--)
{
scanf("%d %d",&v,&val);
dis[i][v] = val;
}
}
floyd();
int minn = MAX;
for(int i=1;i<=n;i++)
{
int maxx = -1;
for(int j=1;j<=n;j++)
{
if( i!=j )
{
maxx = max( maxx , dis[i][j] );
}
}
if( minn>maxx )
{
u = i;
minn = maxx;
}
}
if(minn == MAX) printf("disjoint\n");
else printf("%d %d\n",u,minn);
}
return 0;
}
K - How Many Tableshttp
题意:对每一组数据,告诉你某2个人间认识,让所有能建立起联系的人凑在一桌(跟婚礼排座位差不多啊有木有),问你一共要多少桌子
idea:并查集
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 1010
using namespace std;
bool pd[N];
int fa[N],n,m,t,sum,x,y;
inline void zero()
{
for(int i=1;i<=n;i++)
fa[i] = i;
}
inline int find(int x)
{
if( fa[x]==x ) return x;
else return fa[x] = find( fa[x] );
}
int main()
{
cin>>t;
int u,v;
while( t-- )
{
sum = 0;
memset(pd,0,sizeof(pd));
scanf("%d %d",&n,&m);
zero();
for(int i=1;i<=m;i++)
{
scanf("%d %d",&u,&v);
x = find(u);
y = find(v);
fa[x] = y;
}
for(int i=1;i<=n;i++)
{
x = find(i);
if( pd[ x ]==false )
{
pd[ x ] = true;
sum++;
}
}
printf("%d\n",sum);
}
return 0;
}
L - Mobile phones
题意:题意:有0、1、2三种操作
0 S:一开始一个S*S的矩阵。
1 A B C:第A行第B列的元素值加上C,C可以为负数。
2 X1 Y1 X2 Y2:查询左上顶点为X1行Y1列,右下顶点为X2行Y2列的子矩阵中元素和。
idea:暴力出奇迹(bushi),看看数据范围暴力更新是不可能的了,二维上差分求前缀和才是正道… 那既然要求前缀和了,那不得上二维树状数组嘛。不过注意为了处理有0,记得整体加上1再用树状数组。
//#pragma GCC optimize(2)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
#define N 1100
using namespace std;
int pd,n,m,c[N][N],x,y,xx,yy,v,ans;
inline int lowbit(int x)
{
return x&(-x);
}
void update(int x,int y,int v)
{
for(int i=x;i<=n;i+=lowbit(i))
{
for(int j=y;j<=n;j+=lowbit(j))
{
c[i][j] += v;
}
}
}
inline int getsum(int x,int y)
{
int h = 0;
for(int i=x;i>=1;i-=lowbit(i))
{
for(int j=y;j>=1;j-=lowbit(j))
{
h += c[i][j];
}
}
return h;
}
int main()
{
cin>>pd>>n;
n++;
while( scanf("%d",&pd)!=EOF )
{
if(pd==3) break;
if( pd==1 )
{
scanf("%d %d %d",&x,&y,&v);
x++;y++;
update(x,y,v);
}
if( pd==2 )
{
scanf("%d %d %d %d",&x,&y,&xx,&yy);
x++;y++;
xx++;yy++;
ans = 0;
ans = getsum(xx,yy) - getsum(xx,y-1) - getsum(x-1,yy) + getsum(x-1,y-1);
cout<<ans<<endl;
}
}
return 0;
}
M - Stars
idea:学树状数组打的第一道模板题了…
注意y是单调不降的,对x用树状数组就好了,跟上题一样还是注意x先加上一再用树状数组。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define M 32100
using namespace std;
int c[M],ans[M],n,x,y,s;
int lowbit(int x)
{
return x&(-x);
}
int getsum(int x)
{
int h=0;
while(x>=1)
{
h += c[x];
x -= lowbit(x);
}
return h;
}
void update(int x)
{
while(x<=M)
{
c[x]++;
x+=lowbit(x);
}
}
int main()
{
cin>>n;
memset(ans,0,sizeof(ans));
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
{
scanf("%d %d",&x,&y);
s = getsum(x+1);
update(x+1);
ans[s]++;
}
for(int i=0;i<n;i++)
{
printf("%d\n",ans[i]);
}
return 0;
}
N - Ultra-QuickSort
题意:给出数 n个数字,求出需要交换的次数使这个序列变为递增序列(就是排好序)。
idea:求逆序对数目,记得离散化,不然树状数组or线段树都存不下。
(我用的线段树求的逆序对,就是想写写线段树而已 QWQ)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 2501000
using namespace std;
struct node
{
int l,r,val;
}t[N];
struct cmp
{
int val,rank,xu;
}a[501000];
long long n,m,u,add[N];
inline bool came(cmp x,cmp y)
{
return x.val<y.val;
}
inline bool come(cmp x,cmp y)
{
return x.xu>y.xu;
}
inline void build(int p,int l,int r)
{
int ls = p<<1,rs = p<<1|1,mid = (l+r)>>1;
t[p].l = l;
t[p].r = r;
if(l!=r)
{
build(ls,l,mid);
build(rs,mid+1,r);
t[p].val = 0;
}
else t[p].val = 0;
}
inline void pushdown(int p)
{
int ls = p<<1,rs = p<<1|1;
if( add[p]!=0 && t[p].l!=t[p].r )
{
add[ls] += add[p];
add[rs] += add[p];
t[ls].val += add[p]*( t[ls].r-t[ls].l+1 );
t[rs].val += add[p]*( t[rs].r-t[rs].l+1 );
add[p] = 0;
}
}
void update(int p,int l,int r,int v)
{
pushdown(p);
int ls = p<<1,rs = p<<1|1,mid = (t[p].l+t[p].r)>>1;
if( t[p].l==l&&t[p].r==r )
{
add[p] += v;
t[p].val += (t[p].r-t[p].l+1)*v;
return ;
}
if(t[p].l==t[p].r) return ;
if(r<=mid) update(ls,l,r,v);
else if(l>mid) update(rs,l,r,v);
else
{
update(ls,l,mid,v);
update(rs,mid+1,r,v);
}
t[p].val = t[ls].val + t[rs].val;
}
inline int query(int p,int l,int r)
{
pushdown(p);
int ls = p<<1,rs = p<<1|1,mid = (t[p].l+t[p].r)>>1;
if( t[p].l==l&&t[p].r==r ) return t[p].val;
if(r<=mid) return query(ls,l,r);
else if(l>mid) return query(rs,l,r);
else
{
return ( query(ls,l,mid) + query(rs,mid+1,r) );
}
}
int main()
{
// freopen("in.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
if(n==0) break;
memset(add,0,sizeof(add));
long long ans = 0;
memset(t,0,sizeof(t));
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].val);
a[i].xu = i;
}
sort(a+1,a+n+1,came);
for(int i=1;i<=n;i++)
{
a[i].rank = i;
}
sort(a+1,a+n+1,come);
build(1,1,n);
for(int i=1;i<=n;i++)
{
ans+=query(1,1,a[i].rank);
update(1,a[i].rank,a[i].rank,1);
}
cout<<ans<<endl;
}
return 0;
}
(其实也写了树状数组版本)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#define N 601000
using namespace std;
int n,m,u,c[N];
struct cmp
{
int val,rank,xu;
}a[N];
inline bool came(cmp x,cmp y)
{
return x.val<y.val;
}
inline bool come(cmp x,cmp y)
{
return x.xu>y.xu;
}
int lowbit(int x)
{
return x&(x^(x-1));
}
void update(int x)
{
while(x<=n)
{
c[x]++;
x += lowbit(x);
}
}
int getsum(int x)
{
int h = 0;
while(x>=1)
{
h += c[x];
x -= lowbit(x);
}
return h;
}
int main()
{
// freopen("in.txt","r",stdin);
while(scanf("%d",&n)!=EOF)
{
memset(c,0,sizeof(c));
long long ans = 0;
if(n==0)
{
break;
}
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].val);
a[i].xu = i;
}
sort(a+1,a+n+1,came);
for(int i=1;i<=n;i++)
{
a[i].rank = i;
}
sort(a+1,a+n+1,come);
for(int i=1;i<=n;i++)
{
ans+=getsum(a[i].rank);
update(a[i].rank);
}
cout<<ans<<endl;
}
return 0;
}
O - Discover the Web
题意:
BACK:当前页面进入forward栈,backward栈出栈,如果backward栈为空,当命令不存在。
FORWARD:当前页面进入backward栈,forward栈出栈,如果forward栈为空,当命令不存在。
VISIT:当前页面进入backward栈,该元素为当前页面,清空forward栈。
QUIT:退出浏览器。
idea:超级无敌大模拟大法(但是写起来麻烦的qwq)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<stack>
using namespace std;
int sum=0;
int main()
{
int t;
cin>>t;
while(t--)
{
sum++;
printf("Case %d:\n",sum);
stack<string> forward;
stack<string> backward;
string command,currentpage;
currentpage = "http://www.lightoj.com/";
while( cin>>command )
{
if( command=="QUIT" ) break;
else if( command=="VISIT" )
{
backward.push( currentpage );
cin>>currentpage;
cout<<currentpage<<endl;
while( forward.empty()==0 )
{
forward.pop();
}
}
else if( command=="BACK" )
{
if( backward.empty()==1 )
{
cout<<"Ignored"<<endl;
}
else
{
forward.push( currentpage );
currentpage = backward.top();
backward.pop();
cout<<currentpage<<endl;
}
}
else if( command=="FORWARD" )
{
if( forward.empty()==0 )
{
backward.push( currentpage );
currentpage = forward.top();
forward.pop();
cout<<currentpage<<endl;
}
else
{
cout<<"Ignored"<<endl;
}
}
}
}
return 0;
}
P - 程序设计:蒜头君的数轴
题意:n个点,n-1段距离,插入几个点让所有的点之间的距离变成一样。
然后在无视某两个点之间距离(就是有一段距离可以不管,不用在这两点之间插点)的情况下,求出最少要插进去几个点。
idea:先把各点排好序,再求出各点之间的距离,我们应该比较容易想到求出gcd才能算出要另外插入多少的点,但是问题就是如果我们在循环找看无视第 i 段距离的时候每一次都求一遍其他距离的gcd,会TLE的啊啊啊。这个时候我们可以用两个数组分别存储前缀gcd(第1个距离到第i-1个距离的gcd)和后缀gcd,当无视某段距离时,直接前缀gcd和后缀gcd再求gcd即可。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 101000
using namespace std;
int n,a[N],b[N],t,g1[N],g2[N],m,h=0;
long long ans=999999999;
bool cmp(int x,int y)
{
return x<y;
}
inline int gcd(int a,int b)
{
if(a<b) swap(a,b);
int c;
while( b!=0 )
{
c = a;
a = b;
b = c%b;
}
return a;
}
void work()
{
for(int i=1;i<=m;i++)
{
int g;
long long sum=0;
if(i==1) g = g2[2];
else if( i==m ) g = g1[m-1];
else
{
g = gcd( g1[i-1] , g2[i+1] );
}
sum = (h-b[i])/g - ( m-1 );
ans = min( ans, sum );
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
m = n-1;
sort(a+1,a+n+1);
for(int i=1;i<=m;i++)
{
b[i] = a[i+1] - a[i];
h += b[i];
}
g1[0] = b[1];
for(int i=1;i<=m;i++)
{
g1[i] = gcd( b[i] , g1[i-1] );
}
g2[m] = b[m];
for(int i=m;i>=1;i--)
{
g2[i] = gcd( b[i] , g2[i+1] );
}
work();
printf("%lld",ans);
return 0;
}
Q - Covered Points Count
题意: 给n条线段,让我们求被重复覆盖1次,2次,3次……n次的点分别有多少个
idea:md这题我印象太深的了啊,高二那时候一场模拟赛出了这题,然后我只拿了部分分呜呜呜草(一种植物) 。我们先把每段距离拆开成开始点和结束点并打上标记,再把这些打上标记的点排序。用差分的思想,从第一个开始点到最后一个结束点扫一遍。(记得用long long喔)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 251000
#define LL long long
using namespace std;
struct node
{
LL pos,val;
}e[N*2];
LL n,m,l,r,sum=0,ans[N];
bool cmp(node x,node y)
{
if( x.pos!=y.pos )
return x.pos<y.pos;
else return x.val<y.val;
}
int main()
{
memset(ans,0,sizeof(ans));
cin>>n;
for(LL i=1;i<=n;i++)
{
scanf("%lld %lld",&l,&r);
sum++;e[sum].pos = l; e[sum].val = 1;
sum++;e[sum].pos = r+1; e[sum].val = -1;
}
sort(e+1,e+sum+1,cmp);
LL c,now = 0,last = 0;
for(int i=1;i<=sum;i++)
{
c = e[i].pos;
ans[now] += (c-last);
now += e[i].val;
last = e[i].pos;
}
for(int i=1;i<n;i++)
{
printf("%lld ",ans[i]);
}
cout<<ans[n]<<endl;
return 0;
}
题意:给n个数,问[ l , r ]哪个数字出现次数最多。
idea:RMQ问题这不得上ST表?!?但是注意两个区间连接部分可能会出现数字一样的情况,要对俩区间中间部分连接那里循环计数一下,不可以简单比大小,以及这里存的是某个数在原数组的下标。
#include<iostream>
#include<cstdio>
#include<cstring>
#include <cmath>
#include <stdio.h>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 101000;
int a[N],n;
int m[N][20];
void getst()
{
int i,j;
for(i=0;i<n;i++)
{
m[i][0] = 1;
}
for(j=1;(1<<j)<=n;j++)
{
for(i=0;i+(1<<j)-1<n;i++)
{
if( m[i][j-1] > m[i+(1<<(j-1))][j-1] )
{
m[i][j] = m[i][j-1];
}
else m[i][j] = m[i+(1<<(j-1))][j-1];
int tmp = a[i+(1<<(j-1))-1],x = i+(1<<(j-1))-1,y=x,r = i+(1<<j)-1;
while( x>=i&&a[x]==tmp ) x--;
while( y<=r&&a[y]==tmp ) y++;
int len = y-x-1;
if( len>m[i][j] ) m[i][j] = len;
}
}
}
int query(int l,int r)
{
int k=(int)(log((r-l+1)*1.0)/(log(2.0)));
int ans;
if( m[l][k] < m[r-(1<<k)+1][k] ) ans = m[r-(1<<k)+1][k];
else ans = m[l][k];
int tmp = a[l+(1<<k)-1],x = l+(1<<k)-1,y = x;
while( x>=l&&a[x]==tmp ) x--;
while( y<=r&&a[y]==tmp ) y++;
int len = y-x-1;
if( len > ans ) ans = len;
return ans;
}
int main()
{
int l,r,q,ans;
while( 1 )
{
scanf("%d",&n);
if( n==0 ) break;
scanf("%d",&q);
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]);
}
getst();
while(q--)
{
scanf("%d %d",&l,&r);
ans=query(l-1,r-1);
printf("%d\n",ans);
}
}
return 0;
}
S - A Magic Lamp
题意:对一个数问删掉m个数字后留下来的最小数是多少。
idea:小贪心,对于长度为n的数字来说,删掉m个数字,要想得到的数最小,第一位一定是在[ 1 , 1+m ]里面取得的,假设第一步取了第x位,后面就要在[ x+1 , (m+1) + 1 ]里面找了,一路推过去,注意处理前导零。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 11000
using namespace std;
char s[N],ans[N];
int n,m,l,r,sum;
int main()
{
while( cin>>s>>m )
{
l = 0;
r = m;
sum = 0;
n = strlen(s);
m = n - m;
while(m--)
{
int p = l;
for(int i=l;i<=r;i++)
{
if( s[p] > s[i] ) p = i;
}
ans[sum++] = s[p];
l = p+1;
r++;
}
int head = 0;
while( ans[head]=='0' && head<sum ) head++;
if( head==sum ) printf("0");
else
{
while(head<sum)
{
printf("%c",ans[head]);
head++;
}
}
cout<<endl;
}
return 0;
}