CF# div.2賽後總結
前言
ZWQking AK啦!!!!!!!Orz
A
題意:有\(n\)個小孩,有\(4n\)個位置,要求你安排小孩坐位置,使得被坐的位置的編號\(a,b\)滿足:\(gcd(a,b)≠1,a,b\)。
做法:構造法,讓他們坐\(2n+2,2n+4,2n+6,...,4n\)的位置即可。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; int main() { int T;scanf("%d",&T); while(T--) { int n;scanf("%d",&n); int limit=4*n,ed=2*n; for(int i=limit;i>ed;i-=2)printf("%d ",i); printf("\n"); } return 0; }
B
題意:給你一個字串,\(1\)的位置有地雷,\(0\)沒有,你可以花\(a\)代價引爆連續一段的雷,或者花\(b\)代價埋一顆雷。
做法:
- DP做法,\(dp[i][0/1]\)分別表示這個位置沒有地雷和有地雷的轉移。
- 賽後想了想,可以貪心,計算中間的每一段連續的\(0\)填滿的代價,如果小於\(a\)則填滿,否則不填滿,直接兩端引爆。
我才用的是\(DP\)做法。
兩種做法時間複雜度都是:\(O(n)\)
#include<cstdio> #include<cstring> #define N 110000 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 dp[N][2]; char st[N]; int n,a,b; int main() { int T;scanf("%d",&T); while(T--) { scanf("%d%d",&a,&b); scanf("%s",st+2); n=strlen(st+2)+2; st[1]=st[n]='0'; for(int i=1;i<=n;i++)dp[i][0]=dp[i][1]=999999999; dp[1][0]=0; for(int i=2;i<=n;i++) { if(st[i]=='0') { dp[i][0]=mymin(dp[i-1][0],dp[i-1][1]+a); dp[i][1]=mymin(dp[i-1][0],dp[i-1][1])+b; } else dp[i][1]=mymin(dp[i-1][0],dp[i-1][1]); } printf("%d\n",dp[n][0]); } return 0; }
C
題意: 給你\(a\)陣列和\(b\)陣列,對於每個\(i\),要麼讓小明多花\(b_{i}\)的時間,要麼直接多出一個人花\(a_{i}\)的時間。
然後問你如何安排才可以讓花費時間最大的人最小。
做法:二分答案,還算簡單。
#include<cstdio> #include<cstring> #define N 210000 using namespace std; inline int mymax(int x,int y){return x>y?x:y;} int a[N],b[N],n; inline bool check(int k) { int shit=0; for(int i=1;i<=n;i++) { if(a[i]>k) { shit+=b[i]; if(shit>k)return 0; } } return 1; } int main() { int T;scanf("%d",&T); while(T--) { scanf("%d",&n); int l=1,r=0; for(int i=1;i<=n;i++) { scanf("%d",&a[i]); r=mymax(r,a[i]); } for(int i=1;i<=n;i++)scanf("%d",&b[i]); int mid,ans=r; while(l<=r) { mid=(l+r)>>1; if(check(mid)==1)r=mid-1,ans=mid; else l=mid+1; } printf("%d\n",ans); } return 0; }
D
題意: 給你一個\(a\)陣列,你有兩種操作:
- 選擇一個\(i\),讓\(1\)~\(i\)的位置全部減一。
- 選擇一個\(i\),讓\(i\)~\(n\)的位置全部減一。
問是否可以把\(a\)陣列全部變成\(0\)。
做法:他們都說很簡單,就我想了挺久的。
我們不妨處理\(1\)操作,設\(b\)陣列,\(b_{i}\)表示第\(i\)個位置經過\(1\)操作減去了\(b_{i}\)。
\(b_{i}≥b_{i+1}\)。
這樣,只需要構造一個合法的\(b\)陣列使得\(a\)陣列減完\(b\)陣列後從右往左到最後一個非\(0\)數字非嚴格單調遞減。
然後就開始考慮差分約束,\(b_{i}≥b_{i+1}\),\(i+1\)向\(i\)連一條\(0\)的邊,因為要\(a_{i}-b_{i}≤a_{i+1}-b_{i+1}\),所以\(a_{i}-a_{i+1}+b_{i+1}≤b_{i}\),\(i+1\)向\(i\)連線一條邊權\(a_{i}-a_{i+1}\)的邊,從\(n\)點開始跑最長路,然後最後檢查\(b_{i}≤a_{i}\)即可,但是後面發現了一個事情,邊只會從\(i+1\)連向\(i\),直接一遍掃過去就行了啊(╯‵□′)╯︵┻━┻。
當然,你可以直接預設\(b_{n}\)為\(0\),因為如果\(b_{n}>0\),完全可以把\(1\)~\(n\)的\(1\)操作拆成一個\(1\)操作一個\(2\)操作來搞,所以可以直接預設\(b_{n}=0\)。
時間複雜度:\(O(n)\)
#include<cstdio>
#include<cstring>
#include<queue>
#define N 31000
using namespace std;
int dp[N],n,a[N];
inline int mymax(int x,int y){return x>y?x:y;}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
bool bk=0;
dp[n]=0;
for(int i=n-1;i>=1;i--)
{
if(a[i]>a[i+1])dp[i]=dp[i+1]+a[i]-a[i+1];
else dp[i]=dp[i+1];
if(dp[i]>a[i])
{
bk=1;
break;
}
}
if(!bk)printf("YES\n");
else printf("NO\n");
}
return 0;
}
E
題意:預設現在是字典序最小的全排列(即:\(1,2,3,4,...,n\)),長度為\(n\),然後有兩個操作:
- 統計\([l,r]\)的區間和。
- 假設現在的全排列字典序排名為\(x\),給你一個\(y\),讓你把全排列變成字典序排名為\(x+y\)的全排列。
做法: 和康託展開非常有關係,因為全排列排名總和為\(1e12\),發現\(16!\)已經大於這個數字,暴力維護後面\(16\)個數字,然後暴力統計即可。
時間複雜度:\(O(q16^2)\)
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#define N 210000
#define NN 410000
using namespace std;
typedef long long LL;
int a[N],b[20],n,q;
LL fc[20];
bool v[N];
void kangtuo(int l,LL k)
{
int top=0;
for(int i=l;i<=n;i++)b[++top]=i,v[top]=0;
sort(b+1,b+top+1);
for(int i=top;i>=2;i--)
{
LL shit=k/fc[i-1];k%=fc[i-1];
for(int j=1;j<=top;j++)
{
if(!shit && !v[j])
{
a[top-i+l]=b[j];
v[j]=1;
break;
}
if(!v[j])shit--;
}
}
for(int i=1;i<=top;i++)if(!v[i])a[n]=b[i];
}
LL nowcnt=0;
inline LL getsum(int l,int r){return (LL)(l+r)*(r-l+1)/2;}
int main()
{
scanf("%d%d",&n,&q);
fc[0]=1;for(int i=1;i<=16;i++)fc[i]=fc[i-1]*(LL)i;
for(int i=1;i<=n;i++)a[i]=i;
int ll=n-16+1;
if(ll<=0)ll=1;
for(int i=1;i<=q;i++)
{
int type;scanf("%d",&type);
if(type==1)
{
int l,r;scanf("%d%d",&l,&r);
if(r<ll)printf("%lld\n",getsum(l,r));
else
{
LL sum=0;
int lll=ll;
if(l>=ll)lll=l;
else sum=getsum(l,ll-1);
for(int i=lll;i<=r;i++)sum+=a[i];
printf("%lld\n",sum);
}
}
else
{
int x;scanf("%d",&x);
nowcnt+=x;
kangtuo(ll,nowcnt);
}
}
return 0;
}
F
題意:給你一個\(a\)陣列,長度為\(n\),可以操作\(k\)次,第\(t\)次操作,你可以刪掉第\(i(1≤t≤n-t+1)\)個數字,然後把\(a_{i+1}\)或者\(a_{i-1}\)(必須滿足被貼的數字有意義,即在陣列範圍內,且必須貼數字)貼到\(b\)陣列最右邊(\(b\)陣列一開始為空),然後把\(a_{i}\)~\(a_{n}\)全部往左移一位,現在給你\(a,b\)陣列(\(a,b\)陣列的數字都是不同的),問你操作序列能有多少個。
做法: 首先化一下題意:\(b\)陣列在\(a\)陣列中對應的位置被鎖上了,也就是不能被刪除,然後對於\(b_{1}\),其在\(a\)陣列對應的位置為\(a_{i}\),如果\(i+1,i-1\)的位置都被鎖上了,這個\(b\)陣列絕對得不到,否則,假設\(i-1\)解鎖了,相當於犧牲掉\(i-1\)的位置給\(i\)位置解鎖。
在上述過程中,不難發現,如果對於一個上鎖的位置\(i\),左邊如果沒有被鎖,或者解鎖的時間早於它,那麼在解鎖\(i\)時,左邊一定是未鎖上的位置(因為如果左邊犧牲自己去解鎖更左邊的位置,那麼更左邊就變成了未鎖上的左邊),右邊同理。
橙色為鎖上的位置,紅色為未鎖上的位置,藍色代表犧牲。
這樣,就只需要在開始的時候判斷每個位置是否可以犧牲左邊或者右邊即可。
時間複雜度:\(O(n)\)
#include<cstdio>
#include<cstring>
#define N 210000
using namespace std;
typedef long long LL;
const LL mod=998244353;
int a[N],b[N],c[N],n,k;
inline LL ksm(LL x,LL y)
{
LL ans=1;
for(LL i=1;i<=y;i++)ans=ans*x%mod;
return ans;
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++){scanf("%d",&a[i]);c[i]=0;}
int cnt=0;LL ans=0;
for(int i=1;i<=k;i++)
{
scanf("%d",&b[i]);
c[b[i]]=i;
}
for(int i=1;i<=n;i++)
{
if(c[a[i]]>0)
{
if((c[a[i]]<c[a[i+1]] && c[a[i]]<c[a[i-1]]) || (i==1 && c[a[i+1]]>c[a[i]]) || (i==n && c[a[i-1]]>c[a[i]]))
{
ans=-1;
break;
}
else if(i!=1 && i!=n && c[a[i-1]]<c[a[i]] && c[a[i+1]]<c[a[i]])cnt++;
}
}
if(ans==-1)printf("0\n");
else printf("%lld\n",ksm(2,cnt));
}
return 0;
}