括号序列(bracket)
Time Limit:1000ms Memory Limit:128MB
题目描述
LYK有一个括号序列,但这个序列不一定合法。
一个合法的括号序列如下:
()是合法的括号序列。
若A是合法的括号序列,则(A)是合法的括号序列。
若A和B分别是合法的括号序列,则AB是合法的括号序列。
LYK想通过尽可能少的操作将这个不一定合法的括号序列变成合法的括号序列。一次修改操作是将某个字符变成另一个字符。
你能帮帮它吗?
输入格式(bracket.in)
一行一个字符串S。
输出格式(bracket.out)
一个数表示最少修改次数。
输入样例
()))
输出样例
1
样例解释
将第二个字符修改成(即可。
数据范围
对于30%的数据|S|<=10。
对于60%的数据|S|<=1000。
对于100%的数据|S|<=100000。且|S|是偶数。
一个比较水的题,似乎就是一个比较裸地括号匹配。我们可以用一个比较简单的栈模拟(我用的是队列模拟)就可以A掉的、、
我们每次读入一个字符,然后判断如果这个字符是(那么我们让他进队,即tail++,然后如果读入的字符是一个)那么我们判断在这个字符的前面时候存在(,如果存在,我们就讲这个()全部消掉,即队中的一定只是(,那我们消掉的时候只要让他tail--就好了,当然我们要保证head<=tail。如果对中没有(可以将)消掉,那么说明这个)一定是不合法的我们一定要将这个)进行修改,改成(然后将其入队,tail++。
我们一直这样处理,最后一定会出现这样的情况((((((,如果有)的会就被消掉了啊。。。那么我们只需要将后面的(修改成)就好了,也就是说我们最后进行修改的个数极为(的个数除以2,(的个数即为队中元素的个数,队中的元素的个数为head。
这样这道题就已经AC了
来,看看代码、、、
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 100010 using namespace std; char s[N]; int head,tail,ans; int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') ch=getchar(); while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar(); return x*f; } int main() { freopen("bracket.in","r",stdin); freopen("bracket.out","w",stdout); cin>>s; head=tail=0; int l=strlen(s); for(int i=0;i<l;i++) { if(s[i]=='(') tail++; else { if(tail>head) tail--; else tail++,ans++; } } if(tail!=head) ans+=(tail-head)/2; printf("%d",ans); return 0; }
公交车(bus)
Time Limit:1000ms Memory Limit:128MB
题目描述
LYK在玩一个游戏。
有k群小怪兽想乘坐公交车。第i群小怪兽想从xi出发乘坐公交车到yi。但公交车的容量只有M,而且这辆公交车只会从1号点行驶到n号点。
LYK想让小怪兽们尽可能的到达自己想去的地方。它想知道最多能满足多少小怪兽的要求。
当然一群小怪兽没必要一起上下车,它们是可以被分开来的。
输入格式(bus.in)
第一行三个数k,n,M。
接下来k行每行3个数xi,yi和ci。其中ci表示第i群小怪兽的小怪兽数量。
输出格式(bus.out)
一个数表示最多有多少只小怪兽能满足要求。
输入样例
3 5 3
1 3 4
3 5 2
1 5 3
输出样例
5
样例解释
第一群的3只小怪兽在1号点上车,并在3号点下车。
第二群的2只小怪兽在3号点上车,5号点下车。
数据范围
对于30%的数据小怪兽的总数不超过10只,n<=10。
对于另外30%的数据k,n<=1000。
对于100%的数据1<=n<=20000,1<=k<=50000,1<=M<=100,1<=ci<=100,1<=xi<yi<=n。
不会正解打了个搜索,碰到了30分、、
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<queue> #include<cstdio> #include<vector> #include<cstring> #include<iostream> #include<algorithm> #define N 20010 using namespace std; int n,m,k,ans,sum[N]; 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; } struct node { int to,val; node() {} node(int to,int val):to(to),val(val) {} }; vector<node>G[N]; void DFS(int num,int s,int people) { if(num==n+1) { ans=max(ans,s); return; } people+=sum[num]; sum[num]=0; for(int i=0;i<G[num].size();++i) { int v=G[num][i].to; int ps=G[num][i].val; if(ps<=k-people) { sum[v]+=-ps; DFS(num+1,s+ps,people+ps); } else if((people<k)&&(ps+people>k)) { sum[v]+=people-k; DFS(num+1,s+k-people,k); } } DFS(num+1,s,people); return; } int main() { freopen("bus.in","r",stdin); freopen("bus.out","w",stdout); m=read(),n=read(),k=read(); int x,y,z; for(int i=1;i<=m;++i) { x=read(),y=read(),z=read(); G[x].push_back(node(y,z)); } DFS(1,0,0); printf("%d\n",ans); return 0; }
看到这个题后我们脑子里一定会闪过这样一个经典题目,有n个区间,找尽可能多的区间使得区间之间互不相交。(啊,这不就是线段覆盖那道题吗?!)
对于这道经典的题目我们使用的贪心的做法,将所有的线段按右端点进行排序枚举每一个区间,然后按照能取就取的原则进行贪心。
但是这个题和那个经典题目是有很大的区别的,我们在这个经典问题中的线段是不能相交的,并且我们知道车有一个容量,我们每次都可以上c(容量)的人。
我们对于每一个区间,求这一群怪兽能过几只 维护一个f数组 f[i]表示i这个时刻 车上已经坐了几只怪兽了
[X,Y] Z
for (int i=X; i<Y; i++)
MAX=max(MAX,f[i]);
t=min(Z,M-MAX);
for (int i=X; i<Y; i++) f[i]+=t;
ans+=t
cout<<ans;
1.区间加 2.区间查询最大值,看到这两个操作,我们就可以很容易的想到线段树了 这样的时间复杂度为 klgn
这样这个事情就干完了、、
具体的来说就是我们先按右端点进行排序,然后对于每一个怪兽在车上呆的区间进行一个区间查询,然后在能上就上进行一下区间修改,最后统计个数,这样就搞定了
来,上代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 100005 using namespace std; int n,m,c,a,b,x,q,p,ans,minn; 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; } struct Tree { int l,r,w,f; }tree[N]; struct Node { int x,y,z; }node[N]; int cmp(Node a,Node b) { return a.y<b.y; } void build(int k,int l,int r) { tree[k].l=l,tree[k].r=r; if(tree[k].l==tree[k].r) { tree[k].w=c; return ; } int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); tree[k].w=tree[k<<1].w+tree[k<<1|1].w; } void down(int k) { tree[k<<1].f+=tree[k].f; tree[k<<1|1].f+=tree[k].f; tree[k<<1].w+=(tree[k<<1].r-tree[k<<1].l+1)*tree[k].f; tree[k<<1|1].w+=(tree[k<<1|1].r-tree[k<<1|1].l+1)*tree[k].f; tree[k].f=0; } void change(int k) { if(tree[k].l>=a&&tree[k].r<=b) { tree[k].w+=(tree[k].r-tree[k].l+1)*x; tree[k].f+=x; return ; } if(tree[k].f) down(k); int mid=(tree[k].r+tree[k].l)>>1; if(a<=mid) change(k<<1); if(b>mid) change(k<<1|1); tree[k].w=tree[k<<1].w+tree[k<<1|1].w; } void ask(int k) { if(tree[k].r<a||tree[k].l>b) return ; if(tree[k].l==tree[k].r) { minn=min(minn,tree[k].w); return ; } if(tree[k].f) down(k); int mid=(tree[k].r+tree[k].l)>>1; if(a<=mid) ask(k<<1); if(b>mid) ask(k<<1|1); } int main() { m=read(),n=read(),c=read(); build(1,1,n); for(int i=1;i<=m;i++) { node[i].x=read(),node[i].y=read(); node[i].z=read();node[i].y--; } sort(node+1,node+1+m,cmp); for(int i=1;i<=m;i++) { a=node[i].x,b=node[i].y; minn=0x7fffffff;ask(1); if(minn>node[i].z) x=-node[i].z,change(1),ans+=node[i].z; else x=-minn,change(1),ans+=minn; } printf("%d",ans); }
区间求最小值的时候进行修改的时候只修改一个点的值,因为我们线段树维护的是最小值,而非和
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 100005 using namespace std; int n,m,c,a,b,x; long long ans,minn; 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; } struct Tree { int l,r; long long w,f; }tree[N<<2]; struct Node { int x,y,z; }node[N]; int cmp(Node a,Node b) { return a.y<b.y; } void build(int k,int l,int r) { tree[k].l=l,tree[k].r=r; if(tree[k].l==tree[k].r) { tree[k].w=c; return ; } int mid=(l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid+1,r); tree[k].w=min(tree[k<<1].w,tree[k<<1|1].w); } void down(int k) { tree[k<<1].f+=tree[k].f; tree[k<<1|1].f+=tree[k].f; tree[k<<1].w+=tree[k].f; tree[k<<1|1].w+=tree[k].f; tree[k].f=0; } void change(int k) { if(tree[k].l>=a&&tree[k].r<=b) { tree[k].w+=x; tree[k].f+=x; return ; } if(tree[k].f) down(k); int mid=(tree[k].r+tree[k].l)>>1; if(a<=mid) change(k<<1); if(b>mid) change(k<<1|1); tree[k].w=min(tree[k<<1].w,tree[k<<1|1].w); } void ask(int k) { if(tree[k].l>=a&&tree[k].r<=b) { minn=min(minn,tree[k].w); return ; } if(tree[k].f) down(k); int mid=(tree[k].r+tree[k].l)>>1; if(a<=mid) ask(k<<1); if(b>mid) ask(k<<1|1); } int main() { m=read(),n=read(),c=read(); build(1,1,n); for(int i=1;i<=m;i++) { node[i].x=read(),node[i].y=read(); node[i].z=read();node[i].y--; } sort(node+1,node+1+m,cmp); for(int i=1;i<=m;i++) { a=node[i].x,b=node[i].y; minn=0x7fffffff;ask(1); if(minn>node[i].z) x=-node[i].z,change(1),ans+=node[i].z; else x=-minn,change(1),ans+=minn; } printf("%lld",ans); }
解谜游戏(puzzle)
Time Limit:1000ms Memory Limit:128MB
题目描述
LYK进了一家古董店,它很想买其中的一幅画。但它带的钱不够买这幅画。
幸运的是,老板正在研究一个问题,他表示如果LYK能帮他解出这个问题的话,就把这幅画送给它。
老板有一个n*m的矩阵,他想找一个和最大的子矩阵,这个子矩阵可以由四个参数x,y,x2,y2(1<=x<=x2<=n,1<=y<=y2<=m)来表示,表示一个左上角为(x,y),右下角为(x2,y2)的矩阵。
为了让游戏更加有趣,老板给了一个常数P,他想将原来这个矩阵中恰好一个数变为P,使得这个矩阵的最大的子矩阵尽可能大。
老板想知道这个最大值是多少。
你能帮帮LYK吗?
输入格式(puzzle.in)
第一行三个数n,m,P。
接下来n行,每行m个数ai,j描述整个矩阵。
输出格式(puzzle.out)
输出一个数表示答案。
输入样例
3 3 3
-100 3 3
3 -4 3
3 3 3
输出样例
20
样例解释
改变左上角那个数。
数据范围
对于20%的数据n,m<=10。
对于40%的数据n,m<=25。
对于60%的数据n,m<=50。
对于80%的数据n,m<=100。
对于100%的数据1<=n,m<=300,|P|,|ai,j|<=1000。
看到这个题,我们应该脑子里会蹦出这样的一个大水题吧:http://www.cnblogs.com/z360/p/7608115.html这是我们之前做过的·一道求最大矩形的题。
当时的这道题我们是这样做的:预处理出每一行的前缀和,然后暴力枚举矩形的上边界以及下边界,然后在枚举列,每当统计出的s出现负值,则说明出现了被吃掉的巧克力,更新s为0,然后在继续枚举。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 500 #define maxn 0x7fffffff using namespace std; long long n,m,s,x,y,a[N][N],sum[N][N],v1[N],v2[N],ans; int main() { scanf("%lld%lld",&n,&m); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { scanf("%lld",&a[i][j]); if(!a[i][j]) a[i][j]=-maxn; sum[i][j]+=sum[i-1][j]+a[i][j]; } for(int i=0;i<n;i++) for(int j=i+1;j<=n;j++) { for(int k=1;k<=m;k++) { s+=sum[j][k]-sum[i][k]; if(s<0) s=0; ans=max(ans,s); } s=0; } printf("%lld",ans); return 0; }
我们看着两个题的不同点,这个题我们要在这个矩形内改一个数来是这个矩形内的所有数的和变大,那么我们可以枚举将哪个点进行修改,N^2枚举需要进行修改的点,然后我们在采取上面的做法,我们就可以把N^5的得出答案。
我们考虑一下怎么进行优化,我们知道我们要讲一个矩形内的数进行修改的话,我们考虑修改呢个数对这个矩形造成的(负面)影响最小,即为是这个矩形尽可能地大,我们是不是就要找一个最小的数进行修改。那么我们可以预处理出每一个矩形内的最小值,以及每一个矩形内的所有数的和。然后我们N^4枚举一个矩形的左上角以及右下角,
(明明打了个60分的做法,但是由于题目意思理解的不是很透彻,然后wa掉了好几个点、、题目中说必须要改一个点的权值但是改的这个权值不一定要是我们选定的这个矩形,那么我们呢是不是就可以判断一下我们的最大的矩形是否为整个矩形,如果是整个矩形的话我们一定要改一个点,但是我们选出的这个矩形若不是整个矩形,我们可以改这个矩形以外的点也就是说我们可以将改与不改取一个max,这样应该就可以拿到60分)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 55 #define LL long long using namespace std; int n,m,p,a[N][N],minn[N][N][N][N],sum,ans,s[N][N][N][N]; 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 main() { freopen("puzzle.in","r",stdin); freopen("puzzle.out","w",stdout); n=read(),m=read(),p=read(); int m1,m2; memset(minn,127,sizeof(minn)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(); for(int h1=1;h1<=n;h1++) for(int l1=1;l1<=m;l1++) for(int h2=h1;h2<=n;h2++) for(int l2=l1;l2<=m;l2++) { s[h1][l1][h2][l2]=s[h1][l1][h2-1][l2]+s[h1][l1][h2][l2-1]+a[h2][l2]-s[h1][l1][h2-1][l2-1]; m1=min(minn[h1][l1][h2-1][l2-1],a[h2][l2]),m2=min(minn[h1][l1][h2-1][l2],minn[h1][l1][h2][l2-1]); minn[h1][l1][h2][l2]=min(m1,m2); } for(int h1=1;h1<=n;h1++) for(int l1=1;l1<=m;l1++) for(int h2=h1;h2<=n;h2++) for(int l2=l1;l2<=m;l2++) { sum=s[h1][l1][h2][l2]; ans=max(ans,sum-minn[h1][l1][h2][l2]+p); } printf("%d",ans); return 0; }
唉,改过对题意理解不清的那个地方就是60分了,呜呜、、、
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #define N 55 #define LL long long using namespace std; int n,m,p,a[N][N],minn[N][N][N][N],sum,ans,s[N][N][N][N]; 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 main() { n=read(),m=read(),p=read(); int m1,m2; memset(minn,127,sizeof(minn)); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(); for(int h1=1;h1<=n;h1++) for(int l1=1;l1<=m;l1++) for(int h2=h1;h2<=n;h2++) for(int l2=l1;l2<=m;l2++) { s[h1][l1][h2][l2]=s[h1][l1][h2-1][l2]+s[h1][l1][h2][l2-1]+a[h2][l2]-s[h1][l1][h2-1][l2-1]; m1=min(minn[h1][l1][h2-1][l2-1],a[h2][l2]),m2=min(minn[h1][l1][h2-1][l2],minn[h1][l1][h2][l2-1]); minn[h1][l1][h2][l2]=min(m1,m2); } for(int h1=1;h1<=n;h1++) for(int l1=1;l1<=m;l1++) for(int h2=h1;h2<=n;h2++) for(int l2=l1;l2<=m;l2++) { sum=s[h1][l1][h2][l2]; if(h1==1&&l1==1&&h2==n&&l2==m) ans=max(ans,sum-minn[h1][l1][h2][l2]+p); else ans=max(ans,sum-minn[h1][l1][h2][l2]+max(p,minn[h1][l1][h2][l2])); } printf("%d",ans); return 0; }
我们再来看AC做法
老师讲解:
枚举左上角 n^2 枚举右下角n^2 枚举修改的数 n^2 求和 n^2 -> n^8 求一个矩阵和,可以通过矩阵前缀和做到O(1) 枚举左上角 n^2 枚举右下角n^2 枚举修改的数 n^2 -> n^6 预处理出每个矩阵的最小值是多少。 n^4 枚举左上角 n^2 枚举右下角n^2 修改的数已知(修改最小的或者不修改) -》n^4 n,m<=300 假如我们不要求修改数,查询最大子矩阵 有n个数,查询最大子段和 O(n) for (i=1; i<=n; i++) f[i]=max(f[i-1]+a[i],a[i]); max{f[i]} = 最大子段和 要求我们修改数 修改的数一定是最小的那个数。 f[i][0]以i结尾并且没有数被修改过的最大和 f[i][1]以i结尾并且有数被修改过的最大和 //a[i] 第i列的和 for (int i=1; i<=n; i++) { f[i][0]=max(f[i-1][0]+a[i],a[i]); f[i][1]=max(f[i-1][1]+a[i],f[i-1][0]+a[i]-MIN[i]+P,a[i]-MIN[i]+P); } max{f[?][0/1]} 是答案 恰好改变一个数
代码:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 305 using namespace std; int n,m,p,ans,d[N],s[N][N],minn[N],a[N][N],dp[N][N]; 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 main() { n=read(),m=read(),p=read(); ans=-1000000000;//一定要记得赋初值 for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) a[i][j]=read(),s[i][j]=s[i-1][j]+a[i][j];//预处理前缀和 for(int i=1;i<=n;i++)//枚举上边界 { for(int j=1;j<=m;j++) minn[j]=a[i][j];//预处理每一列里的最小值 for(int j=i;j<=n;j++)//枚举下边界 { for(int k=1;k<=m;k++) minn[k]=min(minn[k],a[j][k]);// for(int k=1;k<=m;k++) d[k]=s[j][k]-s[i-1][k];//处理出在上边界与下边界围成的这个地方每一列的和 dp[0][1]=-1000000000;//赋初值 for(int k=1;k<=m;k++) { dp[k][0]=max(dp[k-1][0]+d[k],d[k]);//dp[k][0]表示到k这一列还没有修改的情况 ,这种情况只能从两种状态转移过来,一种是他前面哪一行再加上这一行,还有一种是直接从这一行开始 dp[k][1]=max(max(dp[k-1][1]+d[k],d[k]-minn[k]+p),dp[k-1][0]+d[k]-minn[k]+p);//dp[k][1]表示到k这一列已经修改了一个地方 } //这个有三种转移,一种是在这之前已经修改了然后再加上这一行,一种是直接从这一行开始那么要从这一行中选出一个数来进行修改,一种是在这一行之前没有修改过的要在这一行中选一个数进行修改 if(i==1&&j==n)//判断是整个矩形的情况,那么这种情况下必须要在选出的这个矩形中修改一个数 ans=max(ans,dp[m][1]); else for(int k=1;k<=m;k++) ans=max(ans,max(dp[k][1],dp[k][0]));//其他的情况下,我们可以从在我们选定的矩形中修改与不修改选一个大的 } } printf("%d",ans); return 0; }