标签:lin 一个 lse i++ mat 效果 几分钟 完全 个数
好不容易遇到一次简单的div2,竟然才A了三题,可恶的第4题,死活调不出来QAQ。
题意:给你\(T\)组数据,每组数据\(n\)个整数(\(n\)是偶数),分别为\(a_{1},a_2,...,a_n\),每个数字的绝对值都小于等于\(100\)且不为\(0\)。
现在让你求一个长度为\(n\)的\(b\)数组,满足每个数字是整数、绝对值都小于等于\(100\)且不为\(0\)。
题解:很简单啊,对于每个相邻的数字这样处理就行了:\(a[1]*a[2]+(-a[1])*a[2]=0\),所以\(b[1]=a[2],b[2]=-a[1]\),其余类似处理即可。
时间复杂度:\(O(n)\)。
非常SB的我还想了几分钟
#include<cstdio>
#include<cstring>
using namespace std;
int n,a[110];
int main()
{
int T;scanf("%d",&T);
for(int i=1;i<=T;i++)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i+=2)
{
int y=i+1;
printf("%d %d ",-a[y],a[i]);
}
printf("\n");
}
return 0;
}
题意:T组数据,每组数据有个\(n,m\),表示\(nm\)的矩阵(满足每组数据的\(n,m\)加起来小于等于\(250000\)),然后序号为\(1\)~\(nm\)的点在这个矩阵中,然后其会给你每一行从左到右的点的编号,和每一列从上到下的点的编号,但是行与行、列与列之间的相对位置不一定是对的,现在要求你还原这个矩阵。
题解:非常的简单,只要找到包含每一行第一个数字的列,就能得到行的相对位置,直接输出即可,时间复杂度可以到:\(O(nm)\),但是为了偷懒,我用排序快速的打出了\(O(nm\log{nm})\)的打法,虽然慢,但是打的快。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510
#define NN 260000
using namespace std;
struct node
{
int a[N];
}a[N];
int id[NN];bool v[NN];
inline bool cmp(node x,node y){return id[x.a[1]]<id[y.a[1]];}
int n,m;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)scanf("%d",&a[i].a[j]);
v[a[i].a[1]]=1;
}
for(int i=1;i<=m;i++)
{
int x=0;
for(int j=1;j<=n;j++)
{
scanf("%d",&x);
if(v[x]==1)id[x]=j;
}
}
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)printf("%d ",a[i].a[j]);
printf("\n");
}
for(int i=1;i<=n;i++)v[a[i].a[1]]=0;
}
return 0;
}
题意:现在有\(6\)个正整数的\(a\)数组,还有\(n\)个正整数的\(b\)数组(对于任意的\(1≤i≤n,1≤j≤6\),满足\(b_{i}>a_{j}\)),然后要求现在构造一个\(c\)数组,对于\(c_{i}\),其等于\(b_{i}-a{j}\)(\(j\)是自己定的),然后\(c\)数组的权值为最大的数字减去最小的数字,求最小的权值。
题解:我们不妨考虑暴力枚举\(l\)虽然是1e9的级别,然后看看其对应的\(r\)最小能是多少,这个应该怎么维护呢?也就是说\([l,r]\)中必须能包含一个\(c\)数组。
我们不妨用一个数字把每一个\(b_{i}-a_{j}\)保存起来,总共\(6n\)个数字,从小到大排序,然后对于\(l++\),我们只要把所有\(b_{i}-a_{j}<l\)删掉,然后找到另外一个最小的\(b_{i}-a_{k}≥l\)加入进去即可,然后\(r\)取\(max\)。
但是\(l\)移动\(1e9\)次的问题还有解决,我们发现,\(l\)只有移动到\(6n\)个数字才是有用的,于是优化一下,\(l\)就只用跳\(6n\)次了,而每个数字最多被删除一次,也是\(6n\)次,所以就是\(O(nlogn)\)。(实际上用基排可以到\(O(n)\))
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 110000
#define NN 610000
using namespace std;
inline int mymax(int x,int y){return x>y?x:y;}
inline int mymin(int x,int y){return x<y?x:y;}
int a[10],b[N],n;
struct node
{
int x/*数字*/,y/*对应的哪个b[y]*/,next/*下一个b[y]-a[k]*/;
}dp[NN];int las[N],len;
inline bool cmp(node x,node y){return x.x<y.x;}
inline bool cmp2(int x,int y){return x>y;}
int main()
{
memset(las,0,sizeof(las));
for(int i=1;i<=6;i++)scanf("%d",&a[i]);
sort(a+1,a+7,cmp2);//其实没有必要
scanf("%d",&n);
int l,r=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
r=mymax(r,b[i]-a[1]);
for(int j=1;j<=6;j++)
{
len++;
dp[len].x=b[i]-a[j];
dp[len].y=i;
}
}
sort(dp+1,dp+len+1,cmp);
for(int i=len;i>=1;i--)//处理next
{
dp[i].next=las[dp[i].y];
las[dp[i].y]=i;
}
l=dp[1].x;int ans=1000000000;
for(int i=1;i<=len;)
{
ans=mymin(r-l,ans);
while(i<=len && dp[i].x==l)//不断的删除数字
{
if(!dp[i].next)//已经没有数字了
{
printf("%d\n",ans);
return 0;
}
r=mymax(dp[dp[i].next].x,r);
i++;
}
l=dp[i].x;
}
printf("%d\n",ans);
return 0;
}
题意:一个人要卖\(1\)~\(n\)价格的物品(每个物品各一个),每个时间点有两种操作:
现在给你\(2n\)个时间点的操作,\(+\)表示放上物品,\(-\) \(x\)表示一个人来买走了\(x\)价格的物品,现在要求你构造出一个满足要求的放物品序列,没有输出\(NO\)。
题解:设\(a[i]\)为第\(i\)次买走是什么物品,\(id[i]\)满足\(?j∈[id[i],i-1],a[j]<a[i]\),且要求\(id[i]\)是最小的,而对于每个\(+\)号,其隶属于后面第一个\(-\)号,很明显,\(a[i]\)只要放在隶属于\([id[i],i]\)中的任意一个\(+\)号,而且不难发现,\(a[i]\)放在\([1,id[i]-1]\)的位置会导致\(id[i]-1\)错误,所以\(a[i]\)只能且任意放在隶属于\([id[i],i]\)中的任意一个\(+\)号。
当然,不难发现,对于每个数字,在前面已经放完之后,尽量的往前方就行了,用并查集维护。(往后放可能会导致后面的数字放不了)
时间复杂度:\(O(nlogn)\)
#include<cstdio>
#include<cstring>
#define N 110000
#define NN 210000
using namespace std;
int fa[N];
int findfa(int x)
{
if(fa[x]!=x)fa[x]=findfa(fa[x]);
return fa[x];
}
int ll[N],rr[N],a[N];
int id[N],n;
int sta[N],top;
inline int erfen(int x)
{
int l=1,r=top,ans=x,mid;
while(l<=r)
{
mid=(l+r)/2;
if(a[sta[mid]]<a[x])r=mid-1,ans=sta[mid-1]+1;
else l=mid+1;
}
return ans;
}
int lis[N];
int main()
{
// freopen("std.in","r",stdin);
// freopen("std.out","w",stdout);
scanf("%d",&n);
int ed=2*n;
int l1=0,l2=0,pre=1;
for(int i=1;i<=ed;i++)
{
char st[10];
scanf("%s",st+1);
if(st[1]==‘-‘)
{
l1++;scanf("%d",&a[l1]);
ll[l1]=pre;rr[l1]=l2;pre=l2+1;
fa[l1]=l1;
}
else l2++;
}
fa[n+1]=n+1;
id[1]=1;sta[top=1]=1;
for(int i=2;i<=n;i++)
{
id[i]=erfen(i);
while(top && a[sta[top]]<a[i])top--;
sta[++top]=i;
}
for(int i=1;i<=n;i++)
{
int x=findfa(ll[id[i]]);
if(x>rr[i])
{
printf("NO\n");
return 0;
}
else
{
lis[x]=a[i];
fa[x]=x+1;
}
}
printf("YES\n");
for(int i=1;i<=n;i++)printf("%d ",lis[i]);
printf("\n");
return 0;
}
事实上,二分部分可以跟单调栈的弹出合并到一起,并查急可以优化到\(O(nα(n))\)。
所以可以到达\(O(nα(n))\)。
当然,还有严格\(O(n)\)的做法,不难发现,我们的瓶颈在于往后放可能会导致后面的数字放不了,但是我们发现,如果\(a[j]<a[j](i<j)\),那么\(a[i]\)能放到的地方\(a[j]\)也能放到,所以\(a[i]\)往后放,而不是往前放的话,并不会影响\(a[j]\)放置,如果\(a[j]>a[i]\),\(a[i]\)压根就放不到\(a[j]\)能放的位置,\(a[j]\)不就随便放了吗?
然后用单调栈随便维护一下就可以了。
时间复杂度:\(O(n)\)
#include<cstdio>
#include<cstring>
#define N 110000
#define NN 210000
using namespace std;
inline int mymin(int x,int y){return x<y?x:y;}
inline int mymax(int x,int y){return x>y?x:y;}
int sta[NN],top;
int n,lis[N];
int main()
{
scanf("%d",&n);
int ed=2*n,l1=0;
for(int i=1;i<=ed;i++)
{
char st[10];scanf("%s",st);
if(st[0]==‘+‘)l1++,sta[++top]=-l1;
else
{
int x=0;scanf("%d",&x);
bool bk=0;
while(top)
{
if(sta[top]>0 && sta[top]<x)top--;
else if(sta[top]<0)
{
lis[-sta[top]]=x;
top--;
bk=1;
break;
}
else break;
}
if(!bk)
{
printf("NO\n");
return 0;
}
sta[++top]=x;
}
}
printf("YES\n");
for(int i=1;i<=n;i++)printf("%d ",lis[i]);
printf("\n");
return 0;
}
题意:有一个魔法,在\(t\)秒施法时瞬间打掉怪物\(a\)点血量,然后在\(t+1,t+2,...,t+c\)的时间点回复\(b\)点血量,施法有\(d\)秒\(cd\),相当于\(t\)秒施法后,\(t+d\)才能再次施法。
回血效果可以叠加,如果一个时间点有多个血量变化,同时计算,然后问你最多可以打掉多少血的怪物,无限的话输出\(-1\)。
有\(T\)组数据,每组给定魔法\(a,b,c,d\)。
做法:不难发现,\(a>bc\)的话,就是\(-1\),反之,不能打败无限血的怪物,那么很明显,\(t\)时刻放完魔法后,\(t+d\)时刻立马放很明显更加优秀。(你总不可能等他多回一点血再打吧。)
所以放魔法的时间就是:\(1,1+d,1+2d,1+3d...\),那么什么时候达到最大值呢?
考虑回本时间,回本时间就是指\(1\)时刻放完魔法后,在哪个时刻第一次的攻击血量会被其回血血量会上来,不难发现,会本时间就是:\((b-1)/a+2\),设为\(tim\)。
先证明\(≥tim\)再放魔法的话肯定不是最大值:
对于\(k≥tim\)放完魔法,此时\(1\)时刻的攻击已经被完全的消除了(甚至可能回得更多),那么不妨构造新的方案,在\(1\)时刻不攻击,\(1+d\)时刻为真正的\(1\)时刻,此时最大的攻击血量一定不小于原来的方案。
再证明\(<tim\)放魔法一定会更大:
类似的证明方法,在\(k<tim\)的位置实施魔法,不妨证明其比\(1,1+d,...,k-d\)的方案更加优秀,类似的证明,构造新方案,\(1\)不放魔法,\(1+d\)为名正言顺的\(1\)时刻,那么新方案的血量等于\(1,1+d,...,k-d\)的方案,但是原方案比新方案多了个\(1\),且其并未回本,所以原方案\(>\)新方案\(=\)\(1,1+d,...,k-d\)的方案。
然后推推式子就行了。
单次复杂度:\(O(1)\)
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
int main()
{
int T;scanf("%d",&T);
while(T--)
{
LL a,b,c,d;scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
if(a>b*c)printf("-1\n");
else
{
LL tim=(a-1)/b+1;//回本时间-1
LL kao=(tim-1)/d+1;
LL ans=kao*a-(d*b)*kao*(kao-1)/2;
printf("%lld\n",ans);
}
}
return 0;
}
标签:lin 一个 lse i++ mat 效果 几分钟 完全 个数
原文地址:https://www.cnblogs.com/zhangjianjunab/p/13877088.html