阅读须知:以下代码有一些是参考别人的代码或者题解,然后自己再码出来的,所以各路大神如果发现代码和自己的很相似,没错,那就是你的代码(逃。
题目链接
分析:操作涉及点修改、区间求和,维护线段树每个结点的最大值,最小值,区间和,然后就是很裸的一颗线段树了。
线段树入门
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e6+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
struct node{
LL sum, maxn, minn;
//node (LL sum=0, LL maxn=0, LL minn=0):sum(sum),maxn(maxn),minn(minn){}
}tree[N<<2];
void build(int L, int R, int rt){
if(L==R) tree[rt].sum = tree[rt].maxn = tree[rt].minn = 0;
else{
int mid = (L+R)>>1;
build(L,mid,rt<<1);
build(mid+1,R,rt<<1|1);
//pushup(tree[rt],tree[rt<<1],tree[rt<<1|1]);
}
}
node query(int l, int r, int L, int R, int rt){
if(l<=L&&R<=r) return tree[rt];
else{
int mid = (L+R)>>1;
if(r<=mid) return query(l,r,L,mid,rt<<1);
else if(l>mid) return query(l,r,mid+1,R,rt<<1|1);
else{
node u, v, ans;
u = query(l,mid,L,mid,rt<<1);
v = query(mid+1,r,mid+1,R,rt<<1|1);
ans.sum = u.sum+v.sum;
ans.maxn = max(u.maxn,v.maxn);
ans.minn = min(u.minn,v.minn);
return ans;
}
}
}
void update(int l, int r, int L, int R, int rt, LL val){
if(L==l&&r==R) tree[rt].sum = tree[rt].maxn = tree[rt].minn = val;
else{
int mid = (L+R)>>1;
if(r<=mid) update(l,r,L,mid,rt<<1,val);
else if(l>mid) update(l,r,mid+1,R,rt<<1|1,val);
tree[rt].sum = tree[rt<<1].sum+tree[rt<<1|1].sum;
tree[rt].maxn = max(tree[rt<<1].maxn,tree[rt<<1|1].maxn);
tree[rt].minn = min(tree[rt<<1].minn,tree[rt<<1|1].minn);
}
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&m);
build(1,n,1);
for(int i = 0; i < m; i++){
int op,l,r;
LL x;
scanf("%d", &op);
if(op){
scanf("%d%d",&l,&r);
node v = query(l,r,1,n,1);
LL ans = v.sum-v.maxn-v.minn;
printf("%lld\n", ans);
}
else{
scanf("%d%lld",&l,&x);
update(l,l,1,n,1,x);
}
}
return 0;
}
题目链接
分析:操作涉及区间修改,区间求和,线段树结点的区间和,而对于区间修改,则需要加个懒惰标记(修改标记)
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e6+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
struct node{
LL sum, add;
//node (LL sum=0, LL maxn=0, LL minn=0):sum(sum),maxn(maxn),minn(minn){}
}tree[N<<2];
void build(int L, int R, int rt){
if(L==R) tree[rt].sum = tree[rt].add = 0;
else{
int mid = (L+R)>>1;
build(L,mid,rt<<1);
build(mid+1,R,rt<<1|1);
//pushup(tree[rt],tree[rt<<1],tree[rt<<1|1]);
}
}
void pushdown(int rt, int ln, int rn){
if(tree[rt].add){
tree[rt<<1].sum+=tree[rt].add*ln;
tree[rt<<1|1].sum+=tree[rt].add*rn;
tree[rt<<1].add+=tree[rt].add;
tree[rt<<1|1].add+=tree[rt].add;
tree[rt].add = 0;
}
}
node query(int l, int r, int L, int R, int rt){
if(l<=L&&R<=r) return tree[rt];
else{
int mid = (L+R)>>1;
pushdown(rt,mid-L+1,R-mid);
if(r<=mid) return query(l,r,L,mid,rt<<1);
else if(l>mid) return query(l,r,mid+1,R,rt<<1|1);
else{
node u, v, ans;
u = query(l,mid,L,mid,rt<<1);
v = query(mid+1,r,mid+1,R,rt<<1|1);
ans.sum = u.sum+v.sum;
return ans;
}
}
}
void update(int l, int r, int L, int R, int rt, LL val){
if(l<=L&&R<=r){
tree[rt].sum+=(R-L+1)*val;
tree[rt].add+=val;
return ;
}
else{
int mid = (L+R)>>1;
pushdown(rt,mid-L+1,R-mid);
if(r<=mid) update(l,r,L,mid,rt<<1,val);
else if(l>mid) update(l,r,mid+1,R,rt<<1|1,val);
else{
update(l,mid,L,mid,rt<<1,val);
update(mid+1,r,mid+1,R,rt<<1|1,val);
}
tree[rt].sum = tree[rt<<1].sum+tree[rt<<1|1].sum;
}
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&m);
build(1,n,1);
for(int i = 0; i < m; i++){
int op,l,r;
LL x;
scanf("%d", &op);
if(op){
scanf("%d%d%lld",&l,&r,&x);
node v = query(l,r,1,n,1);
printf("%lld\n", v.sum);
}
else{
scanf("%d%d%lld",&l,&r,&x);
update(l,r,1,n,1,x);
}
}
return 0;
}
题目链接
分析:给出c数组,通过ci求b数组的mex,mex就是表示集合中最小的未出现的正整数,即$b(i) = mex{b(j)},j的范围(i-c(i) ~ i-1)对于这个如果b(i-ci)不是1的话,那bi就可以选1,由此可以看出我们只需要把b(i)可选的范围分成两部分1 ~ i-c(i),i-c(i) ~ n,看到分成两部分,就可以想到用线段树(逃,接下来就是关键部分了,因为对于一个b(i),可能前面的c(i)个b都选了1~i-c(i),所以bi的中间值(数学老师常说的假设),设一个值v=i-c(i)来进行查找,小于v就往左区间找,大于或等于就往右区间找,线段树结点维护对于值x最后出现的位置,(这个维护可能有点难理解,就是比如x=1,然后这个时候他最后出现的位置是在3,而你在算一个b(4),b(4)对应c为2,则b(4)=mex{b(2),b(3)},而你设置的查找值是v=i-c(i)=2,看左边,x=1,位置3>v,所以不能往左边,要往右边找,差不多是这个道理(一大堆废话)),每次找出一个b(i)后,就更新b(i)最后出现的位置,嗯,就这样。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e6+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,c[N];
int tree[N<<2];
void update(int L, int R, int rt, int pos, int val){
if(L==R){
tree[rt] = val;
return ;
}
int mid = (L+R)>>1;
if(pos<=mid) update(L,mid,rt<<1,pos,val);
else update(mid+1,R,rt<<1|1,pos,val);
tree[rt] = min(tree[rt<<1],tree[rt<<1|1]);
}
int query(int L, int R, int rt, int pos){
if(L==R) return L;
int mid = (L+R)>>1;
if(tree[rt<<1]<pos) return query(L,mid,rt<<1,pos);
else return query(mid+1,R,rt<<1|1,pos);
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d",&n);
for(int i = 1; i <= n; i++) scanf("%d",&c[i]);
mst(tree,-1);
update(1,n+1,1,1,0);
for(int i = 1; i <= n; i++){
int ans = query(1,n+1,1,i-c[i]);
update(1,1+n,1,ans,i);
printf("%d ",ans);
}
return 0;
}
题目链接
分析:题目给一个序列,若干个操作,每个操作是对一个区间进行升序或者降序排序操作,问经过这么多次操作之后第k个元素是什么。(好难),这个时候要靠猜,猜猜这个序列那个才是正确的答案,那就随便猜一个,比如k位置的元素,就叫x吧。好那要看这个元素是否合法呢,就可以以这个数为基准,小于这个元素的值置为0,否则为1,为什么要这么做呢,因为这样有利于用线段树实现排序操作,首先要明确一点,我们只需要知道这个位置的值是否小于x,它具体的值是多少我们不用管,比如对一个区间(0,0,1,0,1)进行升序,就变成了(0,0,0,1,1),降序就是(1,1,0,0,0),回到用线段树实现排序操作,线段树的结点维护这个区间有多少个1,比如有g个,这样升序排序就是对后g个元素置为1,降序就是对前面g个元素置为1,置1操作就是线段树的区间更新。好了讲完用线段树操作之后怎么办,没错,就是检验这个x是否合法,如果k位置为1就说明可能大于等于x,这是合法的,0是小于,所以不合法。那么这样xjb猜太满,而且有很多种合法情况,所以我们就要用二分找答案的方法,如果一顿操作之后k位置为1,那么说明这个数可能是偏小的(如果选的x很大数组里面1就会很少之类的),于是往大的数找,反之亦然。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
struct node{
int op,X,Y;
}gg[N];
int n,k,m;
int a[N],b[N];
int tree[N<<2],add[N<<2];
void build(int L, int R, int rt, int val){
add[rt] = -1;
if(L==R) tree[rt] = (a[L]>=val)?1:0;
else{
int mid = (L+R)>>1;
build(L,mid,rt<<1,val);
build(mid+1,R,rt<<1|1,val);
tree[rt] = tree[rt<<1] + tree[rt<<1|1];
}
}
void pushdown(int L, int R, int mid, int rt){
tree[rt<<1] = (mid-L+1)*add[rt];
tree[rt<<1|1] = (R-mid)*add[rt];
add[rt<<1] = add[rt<<1|1] = add[rt];
add[rt] = -1;
}
int query1(int L, int R, int l, int r, int rt){
if(l<=L&&R<=r) return tree[rt];
int mid = (L+R)>>1;
int ans = 0;
if(add[rt]!=-1) pushdown(L,R,mid,rt);
if(l<=mid) ans+=query1(L,mid,l,r,rt<<1);
if(r>mid) ans+=query1(mid+1,R,l,r,rt<<1|1);
return ans;
}
int query2(int L, int R, int rt, int val){
if(L==R) return tree[rt];
int mid = (L+R)>>1;
if(add[rt]!=-1) pushdown(L,R,mid,rt);
if(val<=mid) return query2(L,mid,rt<<1,val);
else return query2(mid+1,R,rt<<1|1,val);
}
void update(int L, int R, int l, int r, int rt, int val){
if(l<=L&&R<=r){
tree[rt] = (R-L+1)*val;
add[rt] = val;
return ;
}
int mid = (L+R)>>1;
if(add[rt]!=-1) pushdown(L,R,mid,rt);
if(l<=mid) update(L,mid,l,r,rt<<1,val);
if(mid+1<=r) update(mid+1,R,l,r,rt<<1|1,val);
tree[rt] = tree[rt<<1]+tree[rt<<1|1];
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&k);
for(int i = 1; i <= n; i++){
scanf("%d",&a[i]);
b[i] = i;
}
//sort(b+1,b+1+n);
scanf("%d",&m);
for(int i = 0; i < m; i++) scanf("%d%d%d",&gg[i].op,&gg[i].X,&gg[i].Y);
int L = 1, R = n, mid;
while(L<R){
mid = (L+R)>>1;
build(1,n,1,mid+1);
for(int i = 0; i < m; i++){
int num = query1(1,n,gg[i].X,gg[i].Y,1);//cout<<"yes"<
if(num==0) continue;
if(gg[i].op==1){
update(1,n,gg[i].X,gg[i].Y,1,0);
update(1,n,gg[i].X,gg[i].X+num-1,1,1);
}
else{
update(1,n,gg[i].X,gg[i].Y,1,0);
update(1,n,gg[i].Y-num+1,gg[i].Y,1,1);
}
}
if(query2(1,n,1,k)) L = mid+1;
else R = mid;
}
printf("%d\n",L);
return 0;
}
题目链接
分析:有好多种操作,修改、删除、插入,(跟课设好像),对于每个询问输出最贵或者最便宜的,那么就要维护一个有序的集合,这个集合有要方便的用这些操作,只有STL的set莫属啦。(注意题干的重要事情说三遍)
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n;
struct node{
string name;
int V;
bool operator < (const node &t) const{
if(V==t.V) return name<t.name;
return V<t.V;
}
}gg[N];
set<node> st;
map<string,int> mp;
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d", &n);
string thing;
int val,op,cnt = 0;
for(int i = 0; i < n; i++){
cin>>op;
if(op==1){
cin>>thing>>val;
if(mp.count(thing)) continue;
gg[cnt].name = thing;
gg[cnt].V = val;
st.insert(gg[cnt]);
mp[thing] = cnt;
cnt++;
}
else if(op==2){
cin>>thing;
if(!mp.count(thing)) continue;
st.erase(gg[mp[thing]]);
mp.erase(thing);
}
else if(op==3){
cin>>thing>>val;
if(!mp.count(thing)) continue;
st.erase(gg[mp[thing]]);
gg[mp[thing]].V = val;
st.insert(gg[mp[thing]]);
}
else if(op==4){
cin>>val;
if(st.empty()) continue;
if(val==1){
set<node>::iterator it = st.begin();
node u = *it;
cout<<u.name<<endl;
}
else{
set<node>::iterator it = st.end();
it--;
node u = *it;
cout<<u.name<<endl;
}
}
}
return 0;
}
题目链接
分析:给出一维平面上的若干个点,每个点有权值(题目给出的点是有序的),接下来有若干个询问,这个序列中的每个数是否大于或者小于这个数(位置~位置-len)范围的min,avg, max,是就ans++(有点绕,看题干就懂了)。这道题有多种解法,我们可以用队列+线段树维护,队列维护这个数对应的检验范围,然后用线段树对这个范围的数进行查找,线段树结点维护区间的min,max还有和,(没有码出来);另一种是用单调队列,找某个区间的min,就维护一个单调上升队列,找某个区间的max,就维护一个单调下降的队列,队列维护的时候,检验每个数,就是把队列中超过区间范围的数pop掉,然后就可以开心地维护单调队列了。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,c,ans;
struct node{
int X,Y;
}gg[N];
deque<node> Q;
void Deal(bool g_or_l, bool a_or_i, int len){ //g_or_l true is g, false is l; a_or_i true is a, false is i
for(int i = 0; i < n; i++){
while(!Q.empty()&&Q.front().X<gg[i].X-len) Q.pop_front();
if(Q.empty()) Q.push_back(gg[i]);
else{
if(g_or_l){
if(a_or_i){
if(Q.front().Y<gg[i].Y) ans++;
while(!Q.empty()&&Q.back().Y<=gg[i].Y) Q.pop_back();
Q.push_back(gg[i]);
}
else{
if(Q.front().Y<gg[i].Y) ans++;
while(!Q.empty()&&Q.back().Y>=gg[i].Y) Q.pop_back();
Q.push_back(gg[i]);
}
}
else{
if(a_or_i){
if(Q.front().Y>gg[i].Y) ans++;
while(!Q.empty()&&Q.back().Y<=gg[i].Y) Q.pop_back();
Q.push_back(gg[i]);
}
else{
//printf("yes\n");
//printf("%d %d\n",Q.front().Y,gg[i].Y);
if(Q.front().Y>gg[i].Y) ans++;
while(!Q.empty()&&Q.back().Y>=gg[i].Y) Q.pop_back();
Q.push_back(gg[i]);
}
}
}
//printf("%d\n",i);
}
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&c);
for(int i = 0; i < n; i++) scanf("%d%d",&gg[i].X,&gg[i].Y);
for(int i = 0; i < c; i++){
char K[10], fun[10];
int len;
ans = 0;
scanf("%s%s%d",K,fun,&len);
while(!Q.empty()) Q.pop_back();
if(K[0]=='g'){
if(fun[1]=='a') Deal(true,true,len);
else if(fun[1]=='i') Deal(true,false,len);
else{
int sum = 0;
for(int i = 0; i < n; i++){
while(!Q.empty()&&Q.front().X<gg[i].X-len) {
sum-=Q.front().Y;
Q.pop_front();
}
if(Q.empty()) {
Q.push_back(gg[i]);
sum+=gg[i].Y;
}
else{
if((double)gg[i].Y-(double)sum/Q.size()>1e-3) ans++;
Q.push_back(gg[i]);
sum+=gg[i].Y;
}
}
}
}
else{
if(fun[1]=='a') Deal(false,true,len);
else if(fun[1]=='i') Deal(false,false,len);
else{
int sum = 0;
for(int i = 0; i < n; i++){
while(!Q.empty()&&Q.front().X<gg[i].X-len) {
sum-=Q.front().Y;
Q.pop_front();
}
if(Q.empty()) {
Q.push_back(gg[i]);
sum+=gg[i].Y;
}
else{
if((double)gg[i].Y-(double)sum/Q.size()<1e-3) ans++;
Q.push_back(gg[i]);
sum+=gg[i].Y;
}
}
}
}
printf("%d\n",ans);
}
return 0;
}
题目链接
分析:很明显这就是维护一个队列的操作(好像跟课设又是很像?),注意t是升序输入,所以对于每个2,3操作,直接把队首超过t时间的出队(万能的STL)
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n;
struct node{
int ID,x,y;
node (int ID=0, int x=0, int y=0):ID(ID),x(x),y(y){}
};
queue<node> Q;
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d", &n);
for(int i = 0; i < n; i++){
int op,t,a,b;
scanf("%d", &op);
if(op==1){
scanf("%d%d%d",&t,&a,&b);
node u(a,t,t+b-1);
Q.push(u);
}
else if(op==2){
scanf("%d",&t);
while(!Q.empty()){
node u = Q.front(); Q.pop();
if(u.y>=t) break;
}
}
else if(op==3){
scanf("%d",&t);
if(Q.empty()) printf("-1\n");
else{
while(!Q.empty()){
node u = Q.front();
if(u.y>=t) break;
Q.pop();
}
if(Q.empty()) printf("-1\n");
else {
node u = Q.front();
printf("%d\n",u.ID);
}
}
}
}
return 0;
}
题目链接
分析:发现式子就是哈夫曼树的递推式,所以直接优先队列算术来,至于为什么是哈夫曼树
哈夫曼树学习
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
priority_queue<LL,vector<LL>,greater<LL> > pq;
LL x;
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
int T,n; scanf("%d",&T);
while(T--){
while(!pq.empty()) pq.pop();
scanf("%d",&n);
for(int i = 0; i < n; i++){
scanf("%lld",&x);
pq.push(x);
}
LL ans = 0;
while(pq.size()>1){
LL temp = pq.top();
pq.pop();
temp+=pq.top();
pq.pop();
ans+=temp;
pq.push(temp);
}
printf("%lld\n",ans);
}
return 0;
}
题目链接
分析:简单又基础的并查集。
并查集入门
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
int pre[N],cnt[N];
int fin(int x){
if(pre[x]==x) return x;
else return pre[x] = fin(pre[x]);
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) pre[i] = i;
for(int i = 0; i < m; i++){
int x,y,dx,dy;
scanf("%d%d",&x,&y);
dx = fin(x); dy = fin(y);
if(dx!=dy) pre[dy] = dx;
}
for(int i = 1; i <= n; i++) cnt[fin(i)]++;
int temp,t,ans = 0; scanf("%d",&temp);
while(temp--){
scanf("%d", &t);
printf("%d\n",cnt[fin(t)]);
}
return 0;
}
题目链接
分析:有2种不同的信息,同类和克制,有三种,所以我们就给多两个个对立(克制)的集合,然后对每个点拆成x,x+100000,x+200000,表示在不同的克制集合里面,克制的顺序可以看成一个圈(好像本来就是这样子),然后每次给出克制的信息就等于把圈转一转,因为有克制顺序(没有理解的话直接看代码)?用并查集维护。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
int pre[300100];
int fin(int x){
return (pre[x]==x)?x:(pre[x] = fin(pre[x]));
}
void link(int x, int y){
int dx = fin(x);
int dy = fin(y);
if(dx!=dy) pre[dx] = dy;
}
bool judge(int x, int y){
int dx = fin(x);
int dy = fin(y);
if(dx!=dy) return true;
else return false;
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d", &n,&m);
for(int i = 1; i <= n+200000; i++) pre[i] = i;
int flag = -1;
for(int i = 1; i <= m; i++){
int op,x,y;
scanf("%d%d%d",&op,&x,&y);
if(op==2){
if(judge(x,y)&&judge(x+100000,y+100000)&&judge(x+200000,y+200000)&&judge(y,x+100000)&&judge(y+100000,x+200000)&&judge(y+200000,x)){
link(x,y+100000);
link(x+100000,y+200000);
link(x+200000,y);
}
else if(flag==-1) flag = i%3;
}
else if(op==1){
if(judge(x,y+100000)&&judge(x+100000,y+200000)&&judge(x+200000,y)&&judge(y,x+100000)&&judge(y+100000,x+200000)&&judge(y+200000,x)){
link(x,y);
link(x+100000,y+100000);
link(x+200000,y+200000);
}
else if(flag==-1) flag = i%3;
}
}
if(flag==0) flag = 3;
printf("%d\n", flag);
return 0;
}
题目链接
分析:这道题跟上一道题相似,但是不强调克制关系,只是两个对立集合,直接拆点,x就拆成x和x+100000。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
int pre[N<<1];
int fin(int x){
if(pre[x]==x) return x;
else return pre[x] = fin(pre[x]);
}
void link(int x, int y){
int dx = fin(x);
int dy = fin(y);
if(dx!=dy) pre[dy] = dx;
}
bool judge(int x, int y){
int dx = fin(x);
int dy = fin(y);
if(dx==dy) return true;
else return false;
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
cin>>n>>m;
for(int i = 1; i <= n+100000; i++) pre[i] = i;
string op;
int x, y, yn;
for(int i = 0; i < m; i++){
cin>>op;
if(op[0]=='A'){
cin>>x>>y>>yn;
if(yn==1){
link(x,y);
link(x+100000,y+100000);
}
else{
link(x,y+100000);
link(x+100000,y);
}
}
else if(op[0]=='Q'){
cin>>x>>y;
if(judge(x,y)||judge(x+100000,y+100000)) cout<<"1"<<endl;
else if(judge(x,y+100000)||judge(x+100000,y)) cout<<"2"<<endl;
else cout<<"3"<<endl;
}
}
return 0;
}
题目链接
分析:这道题跟上一道题的不同就是对立集合可以有无数个,难度瞬间上升好几个level,简单的并查集会把信息搞掉,所以这里就要用到启发式合并,启发式合并就是对于两棵树的合并,使合并之后树的深度变化尽量小(之类的),这道题,每一个点x维护一个集合,集合里面存储的是与x不同的点,如果x和y不同类则在x的set里加y,y的set里面加x。如果x和y是同类,就把x和y的集合合并,这里就用到启发式合并了,降低复杂度。使集合更高效,x和y就找他们的父节点进行合并,效率又快了一个level(实际上我不怎么会准确算复杂度)。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+5;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,m;
int pre[N];
set<int> st[N];
int fin(int x){
return (pre[x]==x)?x:pre[x]=fin(pre[x]);
}
void link(int x, int y){
int a = fin(x);
int b = fin(y);
if(a!=b){
if(st[b].size()<st[a].size()) swap(a,b); //启发式合并
pre[a] = b;
if(st[a].size()){
for(set<int>::iterator it = st[a].begin(); it != st[a].end(); it++){
st[b].insert(fin(*it));
}
}
}
return ;
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&m);
for(int i = 1; i <= n; i++) pre[i] = i;
for(int i = 0; i < m; i++){
string op; cin>>op;
int x, y, z;
if(op[0]=='A'){
scanf("%d%d%d",&x,&y,&z);
if(z==1) link(x,y);
else{
st[fin(x)].insert(fin(y)); //找父节点效率叫高
st[fin(y)].insert(fin(x));
}
}
else{
scanf("%d%d",&x,&y);
int dx = fin(x);
int dy = fin(y);
if(st[dx].find(dy)!=st[dx].end()||st[dy].find(dx)!=st[dy].end()) printf("2\n");
else if(dx==dy) printf("1\n");
else printf("3\n");
}
}
return 0;
}
题目链接
题目链接
分析:这两道题分别是hzwer大神的分块入门3和分块入门5。
hzwer分块入门
题目链接
分析:给出一组数,操作涉及区间修改,区间查询。(不会分析复杂度,直接给出思路)用分块,好似线段树暴力剪枝可以(还是数据太水?)?,分块的话常规根号n块大小,常规的对块进行修改,左右端点暴力,整块修改。对于每个查询,我们可以用vector存储块内的元素,对块内元素进行排序,找x的时候就对块内元素进行进行二分查找,如果这个区间右修改标记,就把x-atag[块],然后再进行查找,左右不完整的块就暴力查询,复杂度是xx(放弃治疗)。
代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 1e5+66;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
struct node{
int v, id;
}gg[N];
bool cmp(const node &a, const node &b){
if(a.v==b.v) return a.id<b.id;
return a.v<b.v;
}
int n,q,blo;
int bl[N];
LL atag[N];
vector<node> ve[N];
void reset(int a){
ve[a].clear();
for(int i = (a-1)*blo+1; i <= min(a*blo,n); i++) {
//gg[i].v+=atag[bl[a]];
ve[a].push_back(gg[i]);
}
sort(ve[a].begin(),ve[a].end(),cmp);
}
void add(int l, int r, int x){
for(int i = l; i <= min(bl[l]*blo,r); i++) gg[i].v+=x;
reset(bl[l]);
if(bl[l]!=bl[r]){
for(int i = (bl[r]-1)*blo+1; i <= r; i++) gg[i].v+=x;
reset(bl[r]);
}
for(int i = bl[l]+1; i <= bl[r]-1; i++){
atag[i]+=x;
}
}
int query(int x){
int l = INF, r = -INF;
for(int i = 1; i <= bl[n]; i++){
node u;
u.v = x-atag[i];
u.id = 0;
vector<node>::iterator it = lower_bound(ve[i].begin(),ve[i].end(),u,cmp);
//vector::iterator it = ve[i].begin();
if(it == ve[i].end()) continue;
else{
while(it != ve[i].end()){
if((*it).v==u.v){
l = min(l,(*it).id);
r = max(r,(*it).id);
}
else break;
it++;
}
}
}
if(l==INF||r==-INF) return -1;
else return r-l;
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d%d",&n,&q);
blo = sqrt(n);
for(int i = 1; i <= n; i++){
scanf("%d",&gg[i].v);
gg[i].id = i;
}
for(int i = 1; i <= n; i++){
bl[i] = (i-1)/blo+1;
ve[bl[i]].push_back(gg[i]);
}
for(int i = 1; i <= bl[n]; i++) reset(i);
for(int i = 1; i <= q; i++){
int op; scanf("%d",&op);
if(op==1){
int l, r, x; scanf("%d%d%d",&l,&r,&x);
add(l,r,x);
}
else{
int x; scanf("%d",&x);
printf("%d\n",query(x));
}
}
return 0;
}
题目链接
分析:冬马是我的(雾),给出一组数,有若干个查询,每个查询查询两个区间,求这两个区间Σx=0∞(两个区间x出现次数之积),因为只有询问,所以我们很容易想到莫队(鼻青脸肿.jpg),在bi哥的纠正下才知道莫队是对块进行排序,之前一直以为是对询问的l,回归题目,对于每一个询问的一个区间,可以看成是(1r的x的数量)-(1l的x的数量),我们在移动l和r的指针的时候,例如r向右移动的时候,对于新加入的x,那么就对这个询问加上(1l)x的数量,为什么呢,看成这个时候(1~r)是增加了1个x,而l的x数量不变,所以总的就是增加了1*x个。而对于一整个询问,有如下神奇的公式
这张图是来自uestc群里面的题解PPT,讲的很好,很神奇。
即我们只要动态维护每一个区间lr位置的x值前缀就o98k了。
代码(感谢bi哥):
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mst(a,b) memset(a,b,sizeof(a))
#define ALL(x) x.begin(),x.end()
#define INS(x) inserter(x,x.begin())
#define pii pair
#define eps 1e-6
//inline int lowbit(int x){return x&-x;}
//inline int the_mod(int &a,int &b){a=((a)%(b)+(b))%(b);}
const int N = 5e4+200;
const int mod = (int)1e9;
const int INF=0x3f3f3f3f;
const long long LINF=(1LL<<62);
typedef long long LL;
typedef unsigned long long ULL;
//#define LOCAL
const double PI = acos(-1.0);
using namespace std;
int n,q,cnt = 0,blo,l1,l2,r1,r2;
LL v[N],res[N];
int cntl[N], cntr[N];
LL sum = 0;
struct node{
int l,r,bl_id,id,x;
bool operator < (const node &t) const{
if(bl_id==t.bl_id) return r<t.r;
return bl_id<t.bl_id; //莫队精髓,对块进行排序
}
}gg[N<<2];
void add_node(int l, int r, int id, int x){
if(l>r) swap(l,r);
if(l){
gg[++cnt].l = l;
gg[cnt].r = r;
gg[cnt].id = id;
gg[cnt].bl_id = l/blo;
gg[cnt].x = x;
}
}
void add_r(int pos){
sum+=cntl[v[pos]];
cntr[v[pos]]++;
}
void add_l(int pos){
sum+=cntr[v[pos]];
cntl[v[pos]]++;
}
void del_r(int pos){
sum-=cntl[v[pos]];
cntr[v[pos]]--;
}
void del_l(int pos){
sum-=cntr[v[pos]];
cntl[v[pos]]--;
}
int main()
{
#ifdef LOCAL
freopen("test.txt", "r", stdin);
#endif // LOCAL
scanf("%d",&n);
blo = sqrt(n);
for(int i = 1; i <= n; i++) scanf("%lld",&v[i]);
scanf("%d",&q);
for(int i = 1; i <= q; i++){
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
add_node(r1,r2,i,1); //那个神奇的公式
add_node(l1-1,l2-1,i,1);
add_node(l2-1,r1,i,-1);
add_node(l1-1,r2,i,-1);
}
sort(gg+1,gg+1+cnt);
int L = 0, R = 0;
for(int i = 1; i <= cnt; i++){
while(R<gg[i].r){
R++;
add_r(R);
}
while(R>gg[i].r){
del_r(R);
R--;
}
while(L<gg[i].l){
L++;
add_l(L);
}
while(L>gg[i].l){
del_l(L);
L--;
}
res[gg[i].id]+=gg[i].x*sum;
}
for(int i = 1; i <= q; i++) printf("%lld\n",res[i]);
return 0;
}
发现最后两题题目没有放出来,倒数第二题比较简单,线段树分块都可以做,最后一题是GDCPC2018的B题,有点难。
专题补完总结:学了分块,解锁新的并查集姿势(启发式),更熟悉了线段树,复习了单调队列/栈,STL真好用,因为各科作业较多,所以切题速度减慢,完成了课设,各科陆续结课,接下来时间会比较多,所以有更多的时间切专题了,当然还要保证不挂科,最后感谢康师傅的奖励。