题意理解:给定一个m*n的区域,这片区域某个地方存在一个洞,其他地方都有袋鼠,袋鼠会经过一系列运动,可能出界或者掉入洞内,现在给出经过运动变化后剩余袋鼠数量,求洞可能在的位置的可能性
思路解析:
1.将袋鼠的运动转化成矩形形状的变换,经过一系列的运动后,矩形会相应被裁减成一个小矩形,这个小矩形的大小是如果不存在洞的情况下袋鼠剩余个数
2.所以没有出网格的袋鼠数量是x=D-U+1)*(R-L+1),因为最终剩余k只袋鼠,那么就要有x-k只袋鼠掉到洞中
3.我们来统计这个小矩形里活的袋鼠经过在移动过程中经过的格子,也就是统计m*n个格子中每个经过了这个小矩形中多少只袋鼠(注意这个洞可以在m*n的任意位置,而并非只是这个小矩形)然后计算经过数目为(x-k)的格子的数目
这个可以用二维差分,每次移动得到++的矩形范围,在此范围内差分,由于这个小矩形的大小已经确定,并且肯定不会出界,所以判重可以直接用左上角的坐标来判,这样就保证了每只袋鼠对于每个格子只统计一次
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=1010;
int n,m,k;
string s;
int U,D,L,R,u,d,l,r;
bool vis[N][N];
int f[N][N];
void add(int x1,int y1,int x2,int y2)
{
f[x1][y1]++;
f[x2+1][y1]--;
f[x1][y2+1]--;
f[x2+1][y2+1]++;
}
void solve()
{
memset(f,0,sizeof f);
memset(vis,0,sizeof vis);
cin>>n>>m>>k>>s;
U=L=u=l=1;
R=r=m;
D=d=n;
for(int i=0;s[i];i++)
{
if(s[i]=='D')u--,d--;
else if(s[i]=='U')u++,d++;
else if(s[i]=='L')r++,l++;
else r--,l--;
L=max(L,l);
R=min(R,r);
U=max(U,u);
D=min(D,d);
}
if(L>R||U>D)
{
if(k)cout<<0<<endl;
else cout<<m*n<<endl;
return;
}
int delta=(D-U+1)*(R-L+1)-k;
if(delta<0)
{
cout<<0<<endl;
return;
}
add(U,L,D,R);
vis[L][U]=1;
for(int i=0;s[i];i++)
{
if(s[i]=='L')L--,R--;
else if(s[i]=='R')L++,R++;
else if(s[i]=='U')U--,D--;
else U++,D++;
if(vis[L][U])continue;
vis[L][U]=1;
add(U,L,D,R);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
f[i][j]+=f[i-1][j]+f[i][j-1]-f[i-1][j-1];
}
}
int ans=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
if(f[i][j]==delta)ans++;
}
}
cout<<ans<<endl;
}
int main()
{
int t;
cin>>t;
while(t--)
{
solve();
}
}
题意:可以看作一个数轴上有n个点,选取一些点连接,这些点要满足距离不大于k,且这些点将首末相连接,每个点使用的价格不同,接下去有q次临时修改一个点的价格,再求其对应的最小价格
分析:1.如果不做修改的话,这是一个典型的DP问题,每一个点i 可以由前面的j点转移而来,j满足(i-k<=j<i),可以用单调队列来优化,得到O(N)复杂度
得到转移方程:f[i]=min(f[j])+w[i]
2.如果修改的话,对p的改变,会影响p之后所有的点的f[i],时间复杂度达到O(N*Q),需要优化
3.由于只有一个方向的递推,所以影响会一直持续到最后,但是如果从反方向再来一遍递推,也就是预处理出来从n+1向0修建到i所需要的最小代价
转移方程:g[i]=min(g[j]+w[i](i<j<=i+k)
那么,在修建当前点的前提下,总的修建代价就可以得到
VALUE=g[i]+f[i]+w[i]
这样子做的好处在于,对于每一处的修改,它的影响在于自身的w,以及(i,i+k]范围内的f[j],所以时间复杂度就可以降到O(n+kq)
tips:
1.不用stl的话,手写双端队列,我也不知道为什么会TLE
2.大量的读入必须要用scanf,否则第二个点就tle
3.数值很大用long long,相对应的max要写成(long long)1e18,不然第二个点就错
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<deque>
#define INF ((long long) 1e18)
using namespace std;
const int N=5e5+10;
typedef pair<long long,int>pii;
int n,k,t;
long long a[N],f[N],g[N],h[N];
char must[N];
int Q;
int p,v;
int q[N];
void dp(long long f[])
{
// int hh=0,tt=-1;
// q[++tt]=0;
// for(int i=1;i<=n+1;i++)
// {
// while(hh<=tt&&i-q[hh]>k)hh++;
// if(hh<=tt)f[i]=f[q[hh]]+a[i];
// if(must[i]=='1')//这个点必须建,那么它后面的点以它作为滑动窗口的开头是最优的
// {
// hh=0,tt=-1;
// }
// while(hh<=tt&&f[i]<=f[q[tt]])tt--;
// q[++tt]=i;
// }
deque<pii>dq;
f[0]=0;
dq.push_back(pii(0,0));
for(int i=1;i<=n+1;i++)
{
while(dq.front().second<i-k)dq.pop_front();
f[i]=dq.front().first+a[i];
if(must[i]=='1')dq.clear();
while(!dq.empty()&&dq.back().first>=f[i])dq.pop_back();
dq.push_back(pii(f[i],i));
}
}
long long dp2(int x,int y)//把a[x]改成y
{
int tmp=a[x];
a[x]=y;
long long res=INF;
// int hh=0,tt=-1;
// //将f[x-k]--f[x-1]加入单调队列
// for(int i=k;i>0;i--)
// {
//
// if(x-i>=0)
// {
// if(must[x-i]=='1')
// {
// hh=0,tt=-1;
// }
// while(hh<=tt&&f[x-i]<=f[q[tt]])tt--;
// q[++tt]=x-i;
// }
// }
// memcpy(h,f,sizeof f);
// //重新计算f[x]--f[x+k-1]
// for(int i=x;i<=n+1&&i<x+k;i++)
// {
// while(hh<=tt&&i-q[hh]>k)hh++;
// if(hh<=tt)h[i]=h[q[hh]]+a[i];
// res=min(res,h[i]+g[i]);
// if(must[i]=='1')
// {
// hh=0,tt=-1;
// }
// while(hh<=tt&&h[i]<=h[q[tt]])tt--;
// q[++tt]=i;
// }
//
deque<pii> dq;
for (int i = k; i > 0; i--)
if (x - i >= 0) {
if (must[x - i] == '1') dq.clear();
while (!dq.empty() && dq.back().first >= f[x - i]) dq.pop_back();
dq.push_back(pii(f[x - i], x - i));
}
for (int i = x; i < x + k&& i <= n + 1; i++) {
while (dq.front().second < i - k) dq.pop_front();
h[i] = dq.front().first + a[i];
// 计算每个中间点的答案
res = min(res, h[i] + g[i]);
if (must[i] == '1') dq.clear();
while (!dq.empty() && dq.back().first >= h[i]) dq.pop_back();
dq.push_back(pii(h[i], i));
}
a[x]=tmp;
return res;
}
void solve()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
a[n+1]=0;
scanf("%s", must + 1);
dp(f);
reverse(a+1,a+n+1);
reverse(must+1,must+n+1);
dp(g);
reverse(a+1,a+n+1);
reverse(must+1,must+n+1);
reverse(g,g+n+2);
for(int i=1;i<=n;i++)g[i]-=a[i];//g表示i点建立下,后面所有点的最小支出
cin>>Q;
while (Q--) {
int x, y; scanf("%d%d", &x, &y);
printf("%lld\n", dp2(x, y));
}
}
int main()
{
cin>>t;
while(t--)
{
solve();
}
}
题意:
给定一个长度为n的数组,可以在任意长度为m的连续子数组上加一个等差序列,要求这样子操作一次之后得到的序列的第k大值最大
分析
1.要求第k大值的最大值,第k大值意味着这个数组经过操作后中有k-1个数字比这个数字大,所以考虑二分,二分这个第k大数的值,然后统计经过最优操作后,能比这个k值大的数的个数,如果这个个数大于k,说明我们的x取小了,反之,x取大了
2.如果找到最优的方法呢,其实也就是让大于x的数最多的方法(我们计大于x的数标志为1,小于x的为0)
2.1首先,对于数组进行第一次遍历,所有大于x的数打上标记,并统计其个数
2.2然后可以想象,我们的等差数列数组是从左到右依次变大的,然后在从左到右移动其控制范围的过程中,每一个数没有被打上标记的数在它作用过程中,先变到最大,然后逐渐变小,也就是它的标志会先从0-->1,再从1-->0
2.3当作用范围最右端为i时,统计从i-1变到i造成的数据标记变化量,记为f[i]
2.4最后f[i]表示的是,作用范围最右端在这的时候,对于大于x值得数量得贡献量,也就是说,f[]的最大前缀和就是能通过增加这个等差数列而多的满足大于x的值,再与一开始就满足的个数相加,与k比较,得到返回结果
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+10;
int n,k,m,c,d;
int a[N];
int tag[N];
int f[N];
int maxn;
bool check(int x)
{
memset(tag,0,sizeof tag);
int res=0;
for(int i=1;i<=n;i++)
{
if(a[i]>=x)
{
res++;
tag[i]=1;
if(res>=k)return true;//一定加上,因为一开始mid很大,数据过多的情况下不及时return会超时
}
}
memset(f,0,sizeof f);
for(int i=1;i<=n;i++)
{
if(!tag[i])
{
int r=min(m-1,i-1);
if(a[i]+d*r+c<x)continue;//如果最大的也小于那就不用考虑了
else
{
f[max(i,m)]++;//首先它会对当前这个位置产生+1的影响
if(a[i]+c>=x)f[min(n,i+m)]--;//如果最小的加上也大于x,那么就是它离开控制范围的时候--
else
{
long long t = x- a[i] - c;
int pos;
if (t % d == 0) pos = t / d - 1;
else pos = t / d;
f[min(n + 1, i + m - pos - 1)]--;
}
}
}
}
for(int i=m;i<=n+1;i++)
{
if(res>=k)return true;//计算最长前缀和
res+=f[i];
}
return false;
}
signed main()
{
cin>>n>>k>>m>>c>>d;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
int ll=0,rr=1e18;
maxn=c+(m-1)*d;
while(ll<rr)
{
int mid=(ll+rr+1)/2;
if(check(mid))
{
ll=mid;
}
else rr=mid-1;
}
cout<<ll<<endl;
}
tips:
1.对于大量数据的二分,及时return
2.这道题目应该用>=mid来做,如果是>mid的话,实际上把这个数缩小了,只要让小于这个的最大的数排列在k后面就好,所以在对于它影响的--的位置,要分成能不能整除两种情况计算
题意:
一路上有n个事件,每个事件都有三种情况 ,求最后能得到的最大平均
分析:
1.第一二种比较下来,明显是第一种对于目标平均值的优势更大,所以第三种下,我们尽可能选第二种
2.但是选第二种有个条件是必须有两个野兽,并且会导致消失一个野兽,如果一味选择第二种情况,就会导致很快的结束
3.在面对3的情况时,我们只要可以,就选第二种,如果不行再选第一种,并且记录下选第二种的次数,第二种情况是决定生死的情况,所以如果自身的不够,可以反悔之前对于情况三的选择,当时我们是将野兽数目-1,那么现在反悔就是野兽数目加二,同时献祭一个,也就是野兽数目+1
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
int n;
int x;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
int sum=1,cnt=1,choice=0;
bool flag=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&x);
if(x==1)sum++,cnt++;
else if(x==-1)
{
if(cnt>1)cnt--;
else if(choice>=1)choice--,sum++,cnt++;
else flag=1;
}
else
{
if(cnt>1)cnt--,choice++;
else sum++,cnt++;
}
}
if(flag)printf("-1\n");
else
{
cout<<sum/__gcd(sum,cnt)<<" "<<cnt/__gcd(sum,cnt)<<endl;
}
}
return 0;
}
题意:
一个水箱需要几个水阀门能将水放干净
分析:
1.找的是局部最低点,这样的最低点有两种情况,第一种是尖头,类似于v型,第二种则是从高到低再保持直线型的,两种情况不一样
2.对于v型的,必须是从左到右先下后上,也就是叉积>0
3.对于底面平的,没有办法用叉积来计算,判断其x的大小
#include<iostream>
#include<algorithm>
using namespace std;
const int N=2010;
int n,ans;
typedef long long ll;
ll x[N],y[N];
ll cross(ll x1,ll y1,ll x2,ll y2)
{
return x1*y2-x2*y1;
}
int main()
{
cin>>n;
for(int i=0;i<n;i++)cin>>x[i]>>y[i];
for(int i=0,j=1;i<n;i++)
{
while(y[i]==y[j])j=(j+1)%n;
int pre=(i+n-1)%n;
if(y[i]<y[pre]&&y[i]<y[j])
{
if(y[i]!=y[(i+1)%n])
{
if(cross(x[i]-x[pre],y[i]-y[pre],x[j]-x[i],y[j]-y[i])>0)ans++;
}
else
{
if(x[(i+1)%n]>x[i])ans++;
}
}
}
cout<<ans<<endl;
}