洛谷 P1531 I Hate It
洛谷 P1816 忠诚
洛谷 P1198 [JSOI2008]最大数
洛谷 P1972 [SDOI2009]HH的项链
洛谷 P2056 采花
SPOJ KQUERY - K-query
HDU 1394 Minimum Inversion Number
某大佬的博客:https://www.cnblogs.com/PJQOOO/p/4660854.html
题目分析:
一道很裸的单点更新,单点查询,区间查询,直接上代码(线段树)
#include
#include
#include
#include
#include
#define ls (rt<<1)
#define rs (rt<<1|1)
#define mid ((tree[rt].r+tree[rt].l)>>1)
using namespace std;
struct arr{
int max,l,r;
}tree[1000000];
int n,m;
char s[2];
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline void pushup(int rt) { tree[rt].max=max(tree[ls].max,tree[rs].max); }
void build(int l,int r,int rt) {
tree[rt].l=l;tree[rt].r=r;
if(l==r) return ;
int midd=(l+r)>>1;
build(l,midd,ls);build(midd+1,r,rs);
pushup(rt);
}
void change(int l,int r,int c,int rt) {
if(tree[rt].l==l&&tree[rt].r==r) { tree[rt].max=c; return ; }
if(r<=mid) change(l,r,c,ls);
else if(l>mid) change(l,r,c,rs);
else{ change(l,mid,c,ls); change(mid+1,r,c,rs); }
pushup(rt);
}
int query(int l,int r,int rt) {
if(tree[rt].l==l&&tree[rt].r==r) { return tree[rt].max; }
if(r<=mid) return query(l,r,ls);
else if(l>mid) return query(l,r,rs);
else{ return max(query(l,mid,ls),query(mid+1,r,rs)); }
pushup(rt);
}
int QUERY(int l,int r,int rt) {
if(tree[rt].l==l&&tree[rt].r==r) { return tree[rt].max; }
int ans=0;
if(r<=mid) ans+=QUERY(l,r,ls);
else if(l>mid) ans+=QUERY(l,r,rs);
else{ ans+=QUERY(l,mid,ls); ans+=QUERY(mid+1,r,rs); }
pushup(rt);
return ans;
}
int main(){
n=read();m=read();
build(1,n,1);
for(register int i=1;i<=n;++i) {int x=read();change(i,i,x,1);}
for(register int i=1;i<=m;++i) {
cin>>s;int l=read(),r=read();
if(s[0]=='Q') {
printf("%d\n",query(l,r,1));//区间查询最大值
}
if(s[0]=='U') {
int u=QUERY(l,l,1);//单点查询
if(u1);//如果小于r,则更新
}
}
return 0;
}
题目分析:区间查询最小值,当时为了练RMQ,所以就没打线段树了。
#include
#include
#include
#include
#include
using namespace std;
int n,m;
int f[100500][22],a[100500];
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline void st() {
for(register int j=1;(1<for(register int i=1;i+(1<1<=n;++i)
f[i][j]=min(f[i][j-1],f[i+(1<<(j-1))][j-1]);//记住这里是i+(1<<(j-1))不是i+(1<<(j-1))-1
}
inline int rmq(int l,int r) {
int j=0;
while(1<<(j+1)<=r-l+1) ++j;
return min(f[l][j],f[r-(1<1][j]);//记住这里右边是r-(1<
}
int main(){
n=read();m=read();
memset(f,0x3f,sizeof(f));
for(register int i=1;i<=n;++i) a[i]=read(),f[i][0]=a[i];
st();
for(register int i=1;i<=m;++i) {
int l=read(),r=read();
printf("%d ",rmq(l,r));
}
return 0;
}
题目分析:用线段树做的话,就是动态加点(其实我是觉得这算不上动态加点,只是投机取巧了下)因为题目给m个询问,所以最多会用到m个格子,所以可以一开始的时候建一个1~m的线段树,然后每次要加的时候,将当前区间长度+1的位置更新值就好
#include
#include
#include
#include
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
using namespace std;
const int MAXN=200005;
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
int MOD,m,t,ma[MAXN<<2],n;
void pushup(int rt){ ma[rt]=max(ma[rt<<1],ma[rt<<1|1]);}
void build(int l,int r,int rt){
if(l==r){ ma[rt]=-0x7fffffff; return; }
int mid=l+((r-l)>>1);
build(lson); build(rson);
pushup(rt);
}
void change(int add,int loc,int l,int r,int rt){
if(r==loc&&l==loc){ ma[rt]=add; return; }//如果找到当前位置,就可以更新值
int mid=l+((r-l)>>1);
if(loc<=mid) change(add,loc,lson);
else change(add,loc,rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt){
if(L<=l&&r<=R){ return ma[rt]; }
int mid=l+((r-l)>>1);
int q=-0x7fffffff;
if(L<=mid){q=max(q,query(L,R,lson));}
if(midreturn q;
}
int main(){
m=read();MOD=read();//在此题中,要加进的数肯定是小于m的,所以我们可以投机取巧一下,看A操作
build(1,m,1);
for(int i=1;i<=m;i++){
char c;
scanf(" %c ",&c);
int k=read();
if(c=='A'){ n++;k=((long long)k+t)%MOD; change(k,n,1,m,1);
}else { t=query(n-k+1,n,1,m,1); printf("%d\n",t); }
}
return 0;
}
这道题还有另外一种解法,就是单调栈。因为每次都是查询当前长度的前k个,所以我们可以用单调栈记录单调递增的值和位置即可,具体看代码。
#include
#include
#include
#include
using namespace std;
const int MAXN=200005;
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
int m,MOD,t,stack[MAXN],head,num[MAXN],cnt;
int main(){
m=read();MOD=read();
for(int i=1;i<=m;i++){
char c; scanf(" %c ",&c); int k=read();
//两个操作的k不一样,第一个操作的k是数值,第二个操作的k是位置
if(c=='A'){
k=(k+t)%MOD;
cnt++; while(stack[head]<=k&&head) head--;//弹栈,维护单调栈
stack[++head]=k; num[head]=cnt;//压栈
}else {
int l=1,r=head,mid;
k=cnt-k+1;
while(l<=r){
mid=(l+r)>>1;
if(num[mid]1;//找到最后一个小于k的值
else r=mid-1;
}
printf("%d\n",t=stack[l]);
}
}
return 0;
}
题目分析:这道题是统计区间有多少个不同的数。此题有多种解法(下面给出一种解法,不过本蒟只是意会但不可言传,所以看代码吧)本蒟还是试着讲一下吧,我们用一个数组记录当前这个数字,上一次出现的位置(在此称为前驱吧),每次更新的时候将前驱-1,当前这个数字+1。为什么呢?因为在前驱和当前这个数字,我们在统计个数的时候会将两个都统计进去(树状数组更新时),所以要把前驱-1,当前这个数字+1,最后输出的时候就很方便了。(如果要问为什么可以这样做的话,我就不知道了,可以上网搜博客)
code 1
#include
#include
#include
#include
#include
using namespace std;
const int N=5e4+5,M=2e5+5,INF=1e6+5;
int n,m,a[N],last[INF];
struct data{
int l,r,id,ans;
}q[M];
int c[N],p[N];
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline bool cmp(data a,data b){return a.rint p,int v){ while(p<=n) {c[p]+=v;p+=p&-p;}}
inline int sum(int p){ int res=0; while(p>0) {res+=c[p];p-=p&-p;} return res;}
int main(){
n=read(); for(register int i=1;i<=n;++i) a[i]=read();
m=read(); for(register int i=1;i<=m;++i) q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+1+m,cmp);
for(register int i=1;i<=m;++i) p[q[i].id]=i;
int pos=1;
for(register int i=1;i<=n;++i){
if(last[a[i]]) add(last[a[i]],-1);
add(i,1); last[a[i]]=i;
while(q[pos].r==i) q[pos].ans=sum(q[pos].r)-sum(q[pos].l-1),pos++;//因为可能有多个相同的,所以都处理一次
}
for(register int i=1;i<=m;++i) printf("%d\n",q[p[i]].ans);
return 0;
}
code 2
#include
#include
using namespace std;
struct node{
int l, r, ans, i;
bool operator < (const node & A) const{
if(r != A.r) return r < A.r;
return l < A.l;
}
}q[200007];
int tree[250005], a[250005], f[250005], p[250005];
int pre[250005], hav[2500005];
void add(int i, int x){ if(!i) return; for(; i <= 200005; i += i&-i) tree[i] += x; }
int query(int i){ int s = 0; for(; i; i -=i&-i) s += tree[i]; return s; }
int main(){
int i, j, n, l, r, m;
scanf("%d", &n);
for(i = 1; i <= n; i++) scanf("%d", &a[i]);
for(i = 1; i <= n; i++){ pre[i] = hav[a[i]]; hav[a[i]] = i; }
scanf("%d", &m);
for(i = 1; i <= m; i++){ scanf("%d%d", &q[i].l, &q[i].r); q[i].i = i;}
sort(q + 1, q + i);
for(i = 1; i <= m; i++) p[q[i].i] = i;
j = 1;
for(i = 1; i <= m; i++){
while(j <= q[i].r) add(j, 1), add(pre[j], -1), j++;
q[i].ans = query(q[i].r) - query(q[i].l - 1);
}
for(i = 1; i <= m; i++) printf("%d\n", q[p[i]].ans);
return 0;
}
题目分析:次题与上一道题类似,上一道题求的是有多少不同的数字,这一道题要求的是在一个区间内有多少种不同的数字满足个数大于等于2.因为上题记录了一个前驱,那在这里就记录前驱和前驱的前驱,其他操作像上一道题一样。
#include
#include
#include
#include
using namespace std;
int t[100001],n,m,c,mp[100001],ne[100001],p[100001];
struct gjh{
int l,r,id,ans;
}q[100001];
inline int read(){
int x=0,w=1;char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline void add(int x,int y){while(x<=n)t[x]+=y,x+=x&-x;}
inline int sum(int x){int num=0;while(x>0)num+=t[x],x-=x&-x;return num;}
inline bool cmp(gjh a,gjh b){return a.l==b.l?a.rint main(){
n=read();c=read();m=read();
for(register int i=1;i<=n;i++) mp[i]=read();
for(register int i=n;i>0;i--) ne[i]=p[mp[i]],p[mp[i]]=i;
for(register int i=1;i<=c;i++) if(p[i]&&ne[p[i]])add(ne[p[i]],1);
for(register int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i;
sort(q+1,q+m+1,cmp);//以左端点为第一关键字右端点为第二关键字
for(register int i=1;i<=m;++i) p[q[i].id]=i;
int l=1;
for(register int i=1;i<=m;i++){
while(l<q[i].l){
if(ne[l])add(ne[l],-1);
if(ne[l]&&ne[ne[l]])add(ne[ne[l]],1);
++l;
}
q[i].ans=sum(q[i].r)-sum(q[i].l-1);
}
for(int i=1;i<=m;i++)printf("%d\n",q[p[i]].ans);
return 0;
}
以上代码有部分不是自己手打,因为考试临近,主要是为了体验思想
https://www.cnblogs.com/oneshot/p/4706043.html
https://blog.csdn.net/d891320478/article/details/8745963
题目分析:
这道题主要是考到了离线操作.题意是要我们对于每一个询问区间l到r,比k大的数有多少个?
这样我们可以建一棵线段树(或者是树状数组),叶子节点初始值为1.然后在输入val(也就是题目一开始的序列值)后,我们顺便记录它输入时的顺序.然后按照权值大小拍一下序.
再把q个询问,按照k的大小排序.做的时候对于 每个询问可以把 小于当前询问k 的 数字的 位置 在线段树里置0.因为如果小于k,对于答案没有贡献,所以赋值为0.并且也不会影响到后面的序列,因为k是递增的.
程序代码见上面两个链接,一个是线段树写的,另一个是树状数组写的.
//未AC的代码,不知道哪里错了
#include
#include
#include
#include
#include
using namespace std;
const int MAXQ=2e5+10;
const int MAXN=3e4+10;
struct arr{
int l,r,k,idx;
}Q[MAXQ];
int n,q;
int sum[MAXN<<2],ans[MAXN],val[MAXN],idx[MAXN];
inline int read(){
int x=0,w=1;char ch;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline int cmp(int i,int j) {return val[i]int cmp2(arr a,arr b) {return a.kint l,int r,int pos) {
if(l==r) { sum[pos]=1; return ; }
int mid=(l+r)>>1;
build(l,mid,pos<<1); build(mid+1,r,pos<<1|1);
sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}
void updata(int l,int r,int pos,int x,int c) {
if(l==r) { sum[pos]=c; return ; }
int mid=(l+r)>>1;
if(x<=mid) updata(l,mid,pos<<1,x,c);
else updata(mid+1,r,pos<<1|1,x,c);
sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}
int query(int l,int r,int pos,int ua,int ub) {
if(ua<=l&&ub>=r) { return sum[pos]; }
int mid=(l+r)>>1;
int res=0;
if(uapos<<1,ua,ub);
if(ub>mid) res+=query(mid+1,r,pos<<1|1,ua,ub);
return res;
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
while(~scanf("%d",&n)) {
for(register int i=0;iread(); idx[i]=i; }
sort(idx,idx+n,cmp);
q=read();
for(register int i=0;i<q;++i) {
Q[i].l=read();Q[i].r=read();Q[i].k=read();Q[i].idx=i;
}
sort(Q,Q+q,cmp2);
build(1,n,1);
int p=0;
for(register int i=0;i<q;++i) {
while(p1,n,1,idx[p]+1,0);
p++;
}
ans[Q[i].idx]=query(1,n,1,Q[i].l,Q[i].r);
}
for(register int i=0;i<q;++i) printf("%d\n",ans[i]);
}
return 0;
}
解后反思:
对于给出很多询问的时候,如果在线处理不能符合时限,那么我们就考虑离线处理.而在离线处理时我们可以进行一些类似排序的操作.要根据题目来做,线段树的功能还是很强大的!
题目分析
题目要我们对于每一个序列求一个逆序对数目,最后输出最小值.他只会给你一个长度为n的序列,每次将第一个挪到最后时,求一次逆序对.最暴力的做法,做n次.
现在考虑一下正解,我们先对原序列求一遍逆序对,然后每次将a[i]挪到最后时我们只要将答案该一下即可sum+=n-a[i]-1-a[i],n-a[i]-1是指将a[i]挪到最后面时可以产生的贡献,-1是因为不能包括他自己.而再减一次a[i]是因为原来这个数在第一个位置,他的贡献为a[i],挪走后这a[i]个贡献也没有了.(因为题目保证只会有0—n-1的数字出现)
程序代码
#include
#include
#include
#define LL long long
int bit[5050]={0},n,a[5050];
inline int read(){
int x=0,w=1;char ch;
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') w=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch-48),ch=getchar();
return x*w;
}
inline LL min(LL a,LL b){ return aint lb(int x){ return x&(-x); }
inline LL q(int x){ LL ans=0; while(x){ ans+=bit[x]; x-=lb(x); } return ans; }
inline int c(int x){ while(x<=n){ bit[x]++; x+=lb(x); } return 0; }
int main(){
while(scanf("%d",&n)!=EOF){
memset(bit,0,sizeof(bit));
LL ans=0;
for(int i=1;i<=n;i++){
a[i]=read();a[i]++;
ans+=q(n)-q(a[i]); c(a[i]);
}
LL mn=ans;
mn=min(mn,ans);
for(int i=1;i<=n;i++){ ans+=n-a[i]-(a[i]-1); mn=min(mn,ans); }
printf("%lld\n",mn);
}
return 0;
}