好久没写题了……
最近好像能做出2D了,不过上分还是困难。
比赛传送门
这场出题人作的要死,对于题面内容这里不再提。
贪心、找规律、前缀和、二分
给你 n × m n\times m n×m的网格,你可以在两个网格之间的边上放一个灯,这个灯将会照亮这两个网格。
求出照亮所有的 n × m n\times m n×m个格子的最小需要的灯数。
贪心,只要 n n n和 m m m中间有一个是偶数,那么答案就是总格子数除以二;如果都为奇数,那么先暂时去掉一行,将这部分如上计算,再对剩下这一行贪心考虑。
#include
using namespace std;
typedef long long ll;
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
signed main()
{
int t,n,m;
cin>>t;
while(t--)
{
cin>>n>>m;
ll ans=0;
if(n%2==0)
ans=n/2*m;
else if(m%2==0)
ans=m/2*n;
else{
ans=n/2*m;
ans+=m/2+1;
}
cout<<ans<<endl;
}
return 0;
}
小M在办聚会,他现在想邀请 n n n个人,第 i i i个人有一个数值 a i a_i ai,表示当他到小M家时,如果在场的人(不包括他自己)达到 a i a_i ai,这个人就会留下。
求这场聚会最多可以邀请多少人,人数包括小M。
排序之后贪心,如果 a i a_i ai被邀请了,那么数值小于等于 a i a_i ai的人一定也会被邀请。
#include
using namespace std;
typedef long long ll;
const int maxn=2e5+10,inf=0x3f3f3f3f,mod=1000000007;
ll a[maxn];
signed main()
{
int t,n;
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
ll ans=0,tot=0;
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
tot++;//把这一个叫过来
if(a[i]<=tot)
ans=tot;
}
cout<<ans+1<<endl;
}
return 0;
}
给你一个按照上图规律无限扩展的矩阵。
有 t t t组询问,每组 x 1 , y 1 , x 2 , y 2 x_1,y_1,x_2,y_2 x1,y1,x2,y2四个整数,保证 x 1 ≤ x 2 , y 1 ≤ y 2 x_1\le x_2\ ,y_1\le y_2 x1≤x2 ,y1≤y2,输出 ( x 1 , y 1 ) (x_1,y_1) (x1,y1)到 ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的所有路径中,权值和不同的路径个数。
( x , y ) (x,y) (x,y)表示在第 x x x行 y y y列,只能向下或向右行走。
找规律,组合数学
猜一发所有合法路径不会有权值和相同的情况,那么的话单纯计算路径数量只和 x 2 − x 1 x_2-x_1 x2−x1与 y 2 − y 1 y_2-y_1 y2−y1有关, a n s = ( x 2 − x 1 ) × ( y 2 − y 1 ) + 1 ans=(x_2-x_1)\times (y_2-y_1)+1 ans=(x2−x1)×(y2−y1)+1。
我也不知道怎么证明……
#include
using namespace std;
typedef long long ll;
signed main()
{
ll t,x1,x2,y1,y2;
cin>>t;
while(t--)
{
cin>>x1>>y1>>x2>>y2;
ll n=x2-x1,m=y2-y1,ans;
cout<<n*m+1<<endl;
}
return 0;
}
你将选择一段时间和小C连续在一起度过 x x x天假期。
这里一年有 n n n个月,第 i i i个月有 d i d_i di天,当你在每个月第 j j j日和小C在一起时,他将给你 j j j个拥抱。
求你最多能得到多少拥抱数。
保证 1 ≤ x ≤ d 1 + d 2 + ⋯ + d n 1\le x\le d_1+d_2+\dots +d_n 1≤x≤d1+d2+⋯+dn
贪心+前缀和+二分
首先 n n n已经达到了2e5,所以我们不可能枚举每一天作为起始点或者结束点。考虑枚举每个月时,可以发现将每个月最后一天作为结束点时是最优的,而因为 x ≤ d 1 + d 2 + ⋯ + d n x\le d_1+d_2+\dots +d_n x≤d1+d2+⋯+dn,所以度假期最多只可能跨一次年。
所以这里将 n n n翻倍,将相邻两年拼接起来,记 s u m 2 [ i ] sum2[i] sum2[i]为第 1 1 1月到第 i i i月的累积贡献, s u m 3 [ i ] sum3[i] sum3[i]为第 1 1 1月到第 i i i月的累积天数,当确定了结尾的月份时,在 s u m 3 sum3 sum3上二分出起始点所在月份,中间的月份的贡献通过 s u m 2 sum2 sum2查询,在起始点所在月份计算剩余天数的贡献。
记录其中出现的最大值即可。
#include
using namespace std;
typedef long long ll;
#define int ll
const int maxn=1e6+10,inf=0x3f3f3f3f,mod=1000000007;
int d[maxn],sum[maxn],sum2[maxn],sum3[maxn];
int get(int a,int b,int n)
{//从a月b日开始待n天的贡献
return sum[b+n-1]-sum[b-1];//待最后n天的贡献
}
int getm(int a,int x,int n)
{//从a月结束,查找起始所在月份
int l=1,r=a,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(sum3[a]-sum3[mid-1]>=x)
l=mid+1,ans=mid;
else
r=mid-1;
}
return ans;
}
signed main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
for(int i=1;i<maxn;i++)
sum[i]=sum[i-1]+i;
int n,x,ans=0;
cin>>n>>x;
for(int i=1;i<=n;i++)
cin>>d[i];//
for(int i=1;i<=2*n;i++)
{//月份的累计贡献前缀和
sum2[i]=sum2[i-1];
sum3[i]=sum3[i-1];
if(i<=n)
sum2[i]+=get(i,1,d[i]),sum3[i]+=d[i];
else
sum2[i]+=get(i-n,1,d[i-n]),sum3[i]+=d[i-n];
}
for(int i=n+1;i<=2*n;i++)
{//枚举每个月
int now=get(i,max(1ll,d[i-n]-x+1),min(x,d[i-n])),ret,tar;
if(d[i-n]>=x)
{//该月天数大于x
ans=max(ans,now);
continue;
}
else
ret=x-d[i-n];
tar=getm(i-1,ret,n);//起始月份
now+=sum2[i-1]-sum2[tar];
ret-=sum3[i-1]-sum3[tar];//剩余天数
if(tar<=n)
now+=get(tar,d[tar]-ret+1,ret);
else
now+=get(tar,d[tar-n]-ret+1,ret);
ans=max(ans,now);
}
cout<<ans<<endl;
return 0;
}
哇这题好难搞,看了题解也不是很理解。
给定一个长度为 n n n的数组,其中前 ⌈ n 2 ⌉ \left\lceil\dfrac{n}{2}\right\rceil ⌈2n⌉ 项中第 i i i项的值为 a i a_i ai,后面所有项值均为 x x x。
现在要你确定一个整数 k k k,使得数组中任意一个长度为 k k k的子段和 > 0 >0 >0,若不存在则输出 − 1 -1 −1。
当有一个 k ′ ≤ ⌊ 1 n ⌋ k'\le \left\lfloor\dfrac{1}{n}\right\rfloor k′≤⌊n1⌋符合条件时,可以确定 k = 2 k ′ k=2k' k=2k′一定同样成立,这就说明假如存在解,就一定存在 k > n 2 k>\dfrac {n}{2} k>2n成立。
当 x ⩾ 0 x\geqslant 0 x⩾0时,可以确定最优情况为 k = n k=n k=n,判断即可。
否则接着深入研究,尝试找出对每个长度 k k k,出现的最小 k k k长度子段和。
记 p p p为长度为 k k k的字段和 s s s的差分数组, s 1 = a 1 + a 2 + ⋯ + a k s_1=a_1+a_2+\dots +a_k s1=a1+a2+⋯+ak。
而 s i + 1 = s i + a i + k − a i s_{i+1}=s_{i}+a_{i+k}-a_i si+1=si+ai+k−ai,因为 k > n 2 k>\dfrac {n}{2} k>2n,则 a i + k = x a_{i+k}=x ai+k=x。
即 p = [ s 1 , x − a 1 , x − a 2 , … , x − a n − k ] p=[s_1,x-a_1,x-a_2,\dots ,x-a_n-k] p=[s1,x−a1,x−a2,…,x−an−k]。
当 k k k增大了 1 1 1时,差分数组 p p p的首项 + x +x +x,并去掉最后一项。
任意 k k k长度的首项都可以用前缀和 s u m sum sum求出。
另开一个 d p dp dp数组, d p [ i ] dp[i] dp[i]表示对于 ∑ j = 1 i x − a j \sum\limits_{j=1}^ix-a_j j=1∑ix−aj过程中出现的最小和,用来表示 k = n − i k=n-i k=n−i的差分数组中出现的最小差分前缀和。
然后枚举下 k k k,检查一下 s u m [ k ] + d p [ n − k ] > 0 sum[k]+dp[n-k]>0 sum[k]+dp[n−k]>0,存在则直接输出。
#include
using namespace std;
typedef long long ll;
typedef pair<int,int>pii;
#define int ll
const int maxn=5e5+10,inf=0x3f3f3f3f,mod=1000000007;
//#define DEBUG//#define lowbit(x) ((x) & -(x))//<
void read(){}
template<typename T,typename... T2>inline void read(T &x,T2 &... oth) {
x=0; int ch=getchar(),f=0;
while(ch<'0'||ch>'9'){if (ch=='-') f=1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
if(f)x=-x;
read(oth...);
}
int a[maxn],sum[maxn];
signed main()
{
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
#ifdef DEBUG
freopen("input.in", "r", stdin);
// freopen("output.out", "w", stdout);
#endif
int n,lim,x;
cin>>n;
lim=n/2+(n%2);
for(int i=1;i<=lim;i++)
cin>>a[i],sum[i]=sum[i-1]+a[i];
cin>>x;
for(int i=lim+1;i<=n;i++)
sum[i]=sum[i-1]+(a[i]=x);
if(x>=0)
{
cout<<((sum[n]>0)?n:-1)<<endl;
return 0;
}
vector<int>dp(maxn,0);
for(int i=1,tot=0;i<=lim;i++)
{//dp[i]表示对长度为n-i的串,进行差分叠加过程中出现的最小和
tot+=x-a[i];//差分前缀和
dp[i]=min(dp[i-1],tot);
}
for(int k=lim;k<=n;k++)//枚举长度,首项[1,k]的和
{//k增大1时,右面有一个x并到了首项中
if(sum[k]+dp[n-k]>0)//前缀和,作为s的首项
{//如果其中最小和和
cout<<k<<endl;
return 0;
}
}
cout<<-1<<endl;
return 0;
}