CF Round #679 div2賽後總結
前言
好不容易遇到一次簡單的div2,竟然才A了三題,可惡的第4題,死活調不出來QAQ。
A
題意:給你\(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; }
B
題意: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; }
C
題意:現在有\(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;
}
D
題意:一個人要賣\(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;
}
E
題意:有一個魔法,在\(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;
}