懒得写代码的几个题就恬不知耻……
请原谅我……
1.给你N个数 求平均值最大的子区间
做法
求出最大的一个值为答案
不嫌麻烦可以二分答案根据最大子段和判断
T1
#include
#include
using namespace std;
int n,ans;
int main(){
cin>>n;
for(int i=1;i<=n;++i){
int sum;
cin>>sum;
ans=max(ans,sum);
}
cout<
Else
#include
#include
#define N 100005
using namespace std;
int n;
int s[N];
int l=0,r=0;
bool ok(int k){
int maxl=-1,sigma=0;
int sl=1,sr;
for(int i=1;i<=n;++i){
sigma+=s[i];sr=i;
maxl=max(maxl,sigma);
if(sigma<0)sigma=0,sl=i+1;
}
if((sigma-(sr-sl+1)*k)>=0){
l=sl,r=sr;
return 1;
}
else return 0;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>s[i];
int l=1,r=0x7fffff,mid;
while(l>1;
if(ok(mid))l=mid+1;
else r=mid-1;
}
cout<
2.给定N个数 求 min(ai,ai+1,……,aj)*|i-j|的最大值
做法
求出区间最小值
然后递归最小值左右两边
#include
#include
using namespace std;
int n;
int ans;
int minl;
int sum[N];
int c[N];
void change(int r){
c[r]=num[r];
for(int i=1;i=lowbit(r);r-=lowbit(r))
ret=min(ret, c[r]);
}
return ret;
}
void find(int l,int r){
if(l==r)return ;
int maxt,pos;
for(int i=l;i<=r;++i)
if(sum[i]>maxt){
pos=i;
maxt=sum[i];
}
ans=max((r-l)*mint,ans);
find(l,pos-1);
find(pos+1,r);
}
int main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>sum[i];
find(1,n);
cout<
3.给定N个数 求 min(ai,aj)*|i-j|的最大值
要求线性
做法
一直去掉序列左右两端中小的元素
#include
#include
#define N 100005
using namespace std;
int sum[N];
int main(){
int n;
cin>>n;
for(int i=1;i<=n;++i)
cin>>sum[i];
l=1,r=n;
int ans=0;
while(l
4.给定N个数 要求对于所有的子区间求出其中位数
复杂度要求N^2
做法
维护一个数据结构
支持插入一个数,求出中位数,弹出中位数
#include
#include
#include
#define N 10005
using namespace std;
int s[N];
int median;
struct median_queue{
int size;
priority_queue,less>q1;
priority_queue,greater>q2;
median_queue(){
num=0;
}
void push(int s){
num++;
if(size==1)
q1.push(b);
else{
if(b<=q1.top()){
q1.push(b);
if(q1.size()-q2.size()>1){
q2.push(q1.top());
q1.pop();
}
}
else{
q2.push(b);
if(q1.size()>n;
for(int i=1;i<=n;++i)cin>>s[i];
for(int i=1;i<=n;++i){
clear();
for(int j=i;j<=n;++j){
que.push(s[j]);
median=que.top();
}
}
return 0;
}
5.N行N列的点阵? k个特殊点 (xi,yi) ,安全性 w(a,b)=min(|a-xi|+|b-yi|)
求一条从 (1,1) 到 (n,n) 的路径 ,最大化路径上安全性的最小值
要求平方算法
做法
通过BFS预处理出一个点到最近的特殊点的距离
然后二分路径上离特殊点最近的距离
通过并查集等维护联通性
#include
#include
#include
using namespace std;
const int maxn = 1003;
int que[maxn*maxn][2],cnt=0;
int map[maxn][maxn];
int dis[maxn][maxn];
struct Node{
int x,y;
}node[400];
int fs[5]={1,0,-1,0,1};
int n,m;
void bfs() {
int head=0,tail=cnt;
while(head<=tail) {
int x=que[++head][0],y=que[head][1];
for(int i=0;i<4;i++) {
int xx=x+fs[i],xy=y+fs[i+1];
if(xx>=1&&xx<=n&&xy>=1&&xy<=m&&!map[xx][xy]&&!dis[xx][xy]) {
dis[xx][xy]=dis[x][y]+1;
que[++tail][0]=xx,que[tail][1]=xy;
}
}
}
}
bool vis[maxn][maxn];
bool judge(int ans) {
int head=0,tail=1;
memset(que,0,sizeof que);
memset(vis,0,sizeof vis);
que[1][0]=1,que[1][1]=1,vis[1][1]=1;
while(head=1&&xx<=n&&xy>=1&&xy<=m&&dis[xx][xy]>=ans&&!map[xx][xy]&&!vis[xx][xy]) {
if(xx==n&&xy==m)return true;
que[++tail][0]=xx,que[tail][1]=xy;vis[xx][xy]=1;
}
}
}
return false;
}
int main() {
//freopen("run.in","r",stdin);
//freopen("run.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
scanf("%d",&map[i][j]);
if(map[i][j]==1)que[++cnt][0]=i,que[cnt][1]=j;
}
if(map[1][1]||map[n][m]){
puts("0");return 0;
}
bfs();
int l=0,r=1000006;
int ans;
while(l<=r) {
int mid=(l+r)/2;
if(judge(mid)) {
l=mid+1;
ans=mid;
}
else r=mid-1;
}
printf("%d\n",ans);
return 0;
}
简单的分数规划问题
给定两个序列A B
求一个最大的子区间满足
这段区间的A的区间和除以B的区间和最大
分数规划问题
一般做法是二分答案
二分一个m
是否有一个区间其答案变成m
然后变换成a[l,r]-mb[l,r]>=0
令c[i]=a[i]-mb[i]
所以变成c[l,r]=0等价于是否存在c[i]>=0
因为二分的是实数
所以二分50次
#include
#include
#define N 100005
using namespace std;
int n;
int A[N],B[N];
bool can(double s){
for(int i=1;i<=n;++i)
if(A[i]-b[i]*s>=0)return true;
return false;
}
int main(){
cin>>n;
for(int i=1;i<=n;++i)cin>>A[i]>>B[i];
double l=1,r=10000000,mid;
for(int i=1;i<=50;++i){
mid=(l+r)>>1;
if(can(mid))l=mid+1,ans=mid;
else r=mid+1;
}
cout<
极差
给出N个正整数,求出最长的一段,
使得该段内的最大值减去最小值的结果不超过给出的K。
2096: [Poi2010]Pilots
Time Limit: 30 Sec Memory Limit: 162 MB
Submit: 962 Solved: 501
[Submit][Status][Discuss]
Description
Tz又耍畸形了!!他要当飞行员,他拿到了一个飞行员测试难度序列,
他设定了一个难度差的最大值,在序列中他想找到一个最长的子串,
任意两个难度差不会超过他设定的最大值。耍畸形一个人是不行的,
于是他找到了你。
Input
输入:第一行两个有空格隔开的整数k(\(0\leq k\leq 2*10^{9}\)),n(\(1\leq n\leq 3*10^6\)),
k代表Tz设定的最大值,n代表难度序列的长度。
第二行为n个由空格隔开的整数(1<=ai<=2*10^9),表示难度序列。
Output
输出:最大的字串长度。
Sample Input
3 9
5 1 3 5 8 6 6 9 10
Sample Output
4
(有两个子串的长度为4: 5, 8, 6, 6 和8, 6, 6, 9.最长子串的长度就是4)
1、N<=10^52、N<=3*10^6
做法:单调队列维护单增单减队列
从1到n依次加值
若极差大于k则将区间左端点右移到最小元素的右边
#include
#include
#define N 3000005
#define inf 1000000000
#define ll long long
using namespace std;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
int K,n,ans=1;
int a[N],q1[N],q2[N];
void solve(){
int l1=1,l2=1,r1=0,r2=0,t=1;
for(int i=1;i<=n;i++)
{
while(l1<=r1&&a[i]>=a[q1[r1]])r1--;
while(l2<=r2&&a[i]<=a[q2[r2]])r2--;
q1[++r1]=i;q2[++r2]=i;
while(a[q1[l1]]-a[q2[l2]]>K)
if(q1[l1]
疯狂的染色
每次将[L,R]染成某种颜色。问最后每个的颜色。
并查集维护
首先因为将一段区间染色之后还可能会重新染色
所以之前染的颜色会被后来染的颜色覆盖
所以从后往前染色就不用更改颜色的
并查集维护的是一个点染色的区间后第一个点的位置
然后这样之后只有没染过色的点才能被染色
#include
#include
#define N 1000005
using namespace std;
int l,r;
int n,m,p,q;
int fa[N],res[N];
void read(int &s){
char ch=getcahr();
for(;!isdigit(ch);ch=getchar());
for(s=0;isdigit();s=s*10-'0',ch=getchar());
}
inline int find(int x) {
return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main(){
read(n);read(m);read(p);read(q);
for(int i=n+1;i;i--)fa[i]=i;
for(int i=m;i;i--){
l=(i*p+q)%n+1,r=(i*q+p)%n+1;
if(l>r)swap(l,r);
for(int x=find(l);x<=r;x=find(x))
res[x]=i,fa[x]=find(x+1);
}
for(int i=1;i<=n;i++)printf("%d\n",res[i]);
return 0;
}
N个数M次操作
修改一个数或者询问某一段的最大子段和
N,M<=10^5
GSS 1+2+3
用线段树维护
维护一段区间的最大前缀,最大子段和,最大后缀
利用了分治的思想
因为一段区间被分成两端区间
其最大子段和只有三种情况
所以只要维护这三个值就可以
如果单点修改的话就重建区间就好
#include
#include
#define lc k<<1
#define rc k<<1|1
using namespace std;
const int M=1e5+5,N=M<<2;
struct sgtment{
int sum,gss,lgss,rgss;
}tr[N];
int n,m,a[N];
void updata(int k){
tr[k].sum=tr[lc].sum+tr[rc].sum;
tr[k].lgss=max(tr[lc].lgss,tr[lc].sum+tr[rc].lgss);
tr[k].rgss=max(tr[rc].rgss,tr[rc].sum+tr[lc].rgss);
tr[k].gss=max(max(tr[lc].gss,tr[rc].gss),tr[lc].rgss+tr[rc].lgss);
}
void build(int k,int l,int r){
if(l==r){
tr[k].sum=tr[k].gss=tr[k].lgss=tr[k].rgss=a[l];
return ;
}
int mid=l+r>>1;
build(lc,l,mid);
build(rc,mid+1,r);
updata(k);
}
void change(int k,int l,int r,int pos,int val){
if(l==r){
tr[k].sum=tr[k].gss=tr[k].lgss=tr[k].rgss=val;
return ;
}
int mid=l+r>>1;
if(pos<=mid) change(lc,l,mid,pos,val);
else change(rc,mid+1,r,pos,val);
updata(k);
}
sgtment query(int k,int l,int r,int x,int y){
if(l==x&&r==y) return tr[k];
int mid=l+r>>1;
if(y<=mid) return query(lc,l,mid,x,y);
else if(x>mid) return query(rc,mid+1,r,x,y);
else{
sgtment left,right,result;
left=query(lc,l,mid,x,mid);
right=query(rc,mid+1,r,mid+1,y);
result.sum=left.sum+right.sum;
result.lgss=max(left.lgss,left.sum+right.lgss);
result.rgss=max(right.rgss,right.sum+left.rgss);
result.gss=max(max(left.gss,right.gss),left.rgss+right.lgss);
return result;
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
scanf("%d",&m);
for(int i=1,opt,x,y;i<=m;i++){
scanf("%d%d%d",&opt,&x,&y);
if(opt) printf("%d\n",query(1,1,n,x,y).gss);
else change(1,1,n,x,y);
}
return 0;
}
N个数M次操作
将某个区间的数开方或者询问某一段的子段和
N,M\(\leq 10^{5}\)
暴力维护就可以
用一个标记表示区间被整体开方了几次
大于一定数量其值一定就为0
GSS 1 2 3的改版
每次询问的时候
给定左端点落在的区间和右端点落在的区间
询问最大子段和
N,M<=10^5
做法:维护区间最大前缀和,最大后缀和
因为给定区间左端点所在区间和右端点所在区间
所以求左端点部分用后缀和
求右端点部分用前缀和(GSS 5)
BZOJ 3333
题目大意:给定一个序列,每次选择一个位置,
把这个位置之后所有小于等于这个数的数抽出来,
排序,再插回去,求每次操作后的逆序对数
首先我们每一次操作 对于这个位置前面的数
由于排序的数与前面的数位置关系不变 所以这些数
的逆序对不会变化对于这个位置后面比这个数大的数
由于改变位置的数都比这些数小
所以这些数的逆序对不会变化
说到底就是排序的数的逆序对数改变了
以这些数开始的逆序对没有了
于是就好办了 我们用树状数组统计出以每个数
开始的逆序对数 然后以原数的大小为关键字建立线段树
维护区间最小值
对于每个询问p,我们取出[p,n]中的最小值a[x],
将a[x]清为正无穷,把以a[x]开头的逆序对减掉,
继续找,直到a[p]为正无穷为止
每个数只会被找到1次 所以均摊复杂度O(nlogn)
#include
#include
#include
#include
#define M 500500
#define ls tree[p].lson
#define rs tree[p].rson
using namespace std;
struct abcd{
int lson,rson;
int *num;
}tree[M<<1];int tree_tot;
int n,m,tot,a[M];
pairb[M];
int c[M],f[M];
long long ans;
int* _min(int *x,int *y) {
return *x>=*y?y:x;
}
void Build_Tree(int p,int x,int y) {
int mid=x+y>>1;
if(x==y)
{
tree[p].num=a+mid;
return ;
}
ls=++tree_tot;rs=++tree_tot;
Build_Tree(ls,x,mid);
Build_Tree(rs,mid+1,y);
tree[p].num=_min(tree[ls].num,tree[rs].num);
}
int* Get_Ans(int p,int x,int y,int l,int r) {
int mid=x+y>>1;
if(x==l&&y==r)
return tree[p].num;
if(r<=mid)
return Get_Ans(ls,x,mid,l,r);
if(l>mid)
return Get_Ans(rs,mid+1,y,l,r);
return _min( Get_Ans(ls,x,mid,l,mid) , Get_Ans(rs,mid+1,y,mid+1,r) );
}
inline void Modify(int p,int x,int y,int pos) {
int mid=x+y>>1;
if(x==y)
return ;
if(pos<=mid)
Modify(ls,x,mid,pos);
else
Modify(rs,mid+1,y,pos);
tree[p].num=_min(tree[ls].num,tree[rs].num);
}
inline void Update(int x) {
for(;x<=tot;x+=x&-x)
c[x]++;
}
inline int Get_Ans(int x) {
int re=0;
for(;x;x-=x&-x)
re+=c[x];
return re;
}
int main() {
int i,p;
cin>>n>>m;
for(i=1;i<=n;i++)
scanf("%d",&b[i].first),b[i].second=i;
sort(b+1,b+n+1);
for(i=1;i<=n;i++) {
if(i==1||b[i].first!=b[i-1].first)
++tot;
a[b[i].second]=tot;
}
for(i=n;i;i--)
Update(a[i]),ans+=f[i]=Get_Ans(a[i]-1);
Build_Tree(0,1,n);
printf("%lld\n",ans);
for(i=1;i<=m;i++) {
int *temp;
scanf("%d",&p);
if(a[p]!=0x3f3f3f3f)
do{
temp=Get_Ans(0,1,n,p,n);
ans-=f[temp-a];
*temp=0x3f3f3f3f;
Modify(0,1,n,temp-a);
}while(temp!=a+p);
printf("%lld\n",ans);
}
}
图上有黑白两种点
两种操作:
将某点异色
询问图中某类边的数量
N、M均为10^5
做法:
1.维护图中01,10,00边的数量
单点修改则O(n)修改
2.按照点的大小将点分两种进行维护
令\(lim = sqrt(m)\)
所以对于度数小于lim的点的修改,我们都
直接按上法去做算了,复杂度大约是\(n/2*sqrt(m)\)
那度数大于等于lim的点,我们希望不要每次去查和他相邻的每个点,
那么就分别保存这个点的和它相邻是0的点之间的边权和sum[i][0] 以及
这个点的和它相邻是1的点之间的边权和sum[i][1]。
如果这个点的这个sum值是自己记录的,只会影响到周围度数大于等于lim的点,
所以只要去修改这个点周围度数大于等于lim的点j的sum[j][0]和sum[j][1],
复杂度大约是\(n/2*sqrt(m)\)。
所以总复杂度\(n*sqrt(m)\)
#include
#include
#include
#include
给出N根木棍
有长度有颜色
问能否找到三根颜色不同的木棍组成三角形
木棍总数不超过10^6
考虑恰好组不成三角形的情况
设三条边为\(a_1,a_2,a_3\)
若对于任意\(a_i(1\leq i\leq 3)\),都有\(a_j+a_k>a_i(i,j,k\in{1,2,3}) 则可构成三角形 则若有一\)a_j+a_k=a_i$则恰好构不成一个三角形
观察上述式子,发现他与斐波那契数列的递推公式非常像:
\[f_i=f_{i-1}+f_{i-2}(i\leq i)\]
则易看出
如果一个数列中所有的数任取\(i,j,k\)都不能相互满足\(i+j>k\)
则数列中所有数必定满足,若将数字从小到大排序
则\(a_i\leq a_{i-1}+a_{i-2}\)
所以可以根据这个来判断是否能组成三角形
换种思路,若一个数列的数相互构不成三角形
则数列的大小一定受到限制
对于一般的数据范围不超过int \((2^31-1)\)
斐波那契数在第47项就超过了
所以可以得出结论,如果木棍的数量超过47根
一定存在情况使得存在\(i+j>k\)
SPOJ 16549 QTREE6 - Query on a tree VI
给一棵树
每个点有黑有白
有反色操作
问与每个点联通的块中有多少个点
N,M<=100,000
记录每个点下方黑点和白点联通块点数个数
询问归到最上方结点
链修改单点查询
树状数组
DEC13 QTREE6