2024.1.26 模擬賽

心海秋的墨木仄發表於2024-04-05

A

看到最大的最小,可以想到二分答案。

答案的不公平度可以二分,因此二分然後檢查即可。

檢查的時候假設二分的答案為 \(a\),則一種玩偶如果有 \(x\) 個,就需要至少分給 \(x/a\) 上取整個小朋友。只要看小朋友總數夠不夠即可。於是可以線性時間複雜度檢查。

總時間複雜度 \(O(n\log v)\)

#include <bits/stdc++.h>
using namespace std;
int n,m,a[300010],l=1,r=-1,ans;
int main(){
	//freopen("frog3.in","r",stdin);
	//freopen("frog3.ans","w",stdout);
	cin>>n>>m;
	for(int i=1;i<=m;i++)cin>>a[i],r=max(r,a[i]);
	while(l<=r){
		int kobe=0,mid=(l+r)/2;
		for(int i=1;i<=m;i++)kobe+=ceil(1.0*a[i]/mid);
		if(kobe<=n)ans=mid,r=mid-1;
		else l=mid+1;
	}
	cout<<ans;
	return 0;
}

B

序列的平均值僅由序列中的元素和 \(S\) 以及元素數量 \(N\) 確定(\(S/N\))。可以發現,只要不爆裂,平均值只和發生的總次數與合併的總次數有關。發生和合並的順序(不爆裂的情況),合併的時候選擇哪兩個數都不會影響平均值。

  • 發生:\(N\)\(1\)\(S\)\(1\),會讓答案不變或者變小。(因為新增的是最小的數)
  • 合併:\(N\)\(1\)\(S\) 不變,會讓答案變大。

因此我們要在不爆裂的前提下儘可能進行合併。從前到後列舉音符,能合併就合併,如果爆裂了,那麼就將之前的一次自由選擇由合併改成發生。執行這個貪心策略,時間複雜度 \(O(n)\)

#include<bits/stdc++.h>
#define For(i,l,r) for(int i=(l);i<=(r);++i)
typedef long long ll;
using namespace std;
int gcd(int a,int b){return (b==0)?(a):(gcd(b,(a%b)));}
int n;
void solve()
{
    scanf("%d",&n);
    int w=1,cnt=1,tmp=0;
    bool flag=true;
    For(i,1,n)
    {
        int opt;
        scanf("%d",&opt);
        if(opt==1)
        {
            ++w;
            ++cnt;
        }
        else if(opt==-1)
        {
            if(cnt>1)
                --cnt;
            else if(cnt==1)
            {
                if(tmp==0)
                    flag=false;
                else
                {
                    --tmp;
                    ++w;
                    ++cnt;
                }
            }
        }
        else if(opt==0)
        {
            if(cnt==1)
            {
                ++cnt;
                ++w;
            }
            else
            {
                --cnt;
                ++tmp;
            }
        }
    }
    if(flag==false)
    {
        puts("-1");
        return;
    }
    else if(flag==true)
    {
        int GCD=gcd(w,cnt);
        w/=GCD;
        cnt/=GCD;
        printf("%d %d\n",w,cnt);
        return;
    }
    return;
}
int main()
{
    //freopen("bag2.in","r",stdin);
    //freopen("bag2.ans","w",stdout);
    int t;
    scanf("%d",&t);
    while(t--)
        solve();
    return 0;
}

C

二分答案 \(x\) ,將大於等於 \(x\) 的數看成 \(1\) ,小於 \(x\) 的數看成 \(0\) ,問題轉化為判斷能否透過至多一次變奏,使序列中 \(1\) 的數量大於等於 \(k\)

在操作位置從左往右移的過程中,對於一個初始的 \(0\) ,我們發現其最多變成 \(1\) 一次。

第一次進入操作範圍時會取到最大值,然後慢慢變小。

因此其最多有兩個關鍵位置,分別是變成 \(1\) 的位置和變回 \(0\) 的位置 。

對每個數找出這兩個關鍵位置,這部分是線性的。

我們維護一個初始全為 \(0\) 的陣列 \(f\)\(f_i\) 表示在 \(i\) 位置操作, \(i\) 對應等差數列首項,能有多少個 \(1\) ,若 \(f\) 陣列大於等於 \(k\) ,那麼 \(x\) 合法。

那麼兩個關鍵位置相當於在 \(f\) 上區間加 \(1\)

暴力做是平方的,在 \(f\) 的差分陣列上單點操作後透過字首和還原出 \(f\) 即可做到線性。

總的時間複雜度為 \(O(n \log A)\)\(A\) 為初始二分的右端點,也即可能的答案最大值。

# include <bits/stdc++.h>

using namespace std ;

typedef long long ll ;
const ll INF = 1e18 + 1e10; 
int n , k , m , c , d ;
ll a[200005] ;
int de[200005] ;
void update( int l , int r )
{
	if ( l > r ) return ;
	de[l] ++ , de[r + 1] -- ;
}
bool check( ll v )
{
	for ( int i = 1 ; i <= n - m + 1 ; i++ ) de[i] = 0 ;
//	puts("OK") ;
	for ( int i = 1 ; i <= n ; i++ )
	{
		if ( a[i] >= v ) de[1] ++ ;
		else
		{
			ll tmp ;
			if ( d )
			{
				tmp = ( v - a[i] - c + d - 1 ) / d ;
				if ( tmp < 0 ) tmp = 0 ;
				if ( tmp >= m ) continue ;
			}
			else 
			{
				if ( a[i] + c >= v ) tmp = 0 ;
				else continue ;
			}
			ll r = i - tmp , l = i - m + 1 ;
//			printf("%lld %lld\n" , l , r) ;
			update( max( 1ll , l ) , min( (ll)(n - m + 1) , r ) ) ;
		}
	}
	int maxn = 0 , num = 0 ;
	for ( int i = 1 ; i <= n ; i++ ) 
	{
		num += de[i] ;
		maxn = max( maxn , num ) ;
	}
	return maxn >= k ;
}

int main()
{
//	freopen("array2.in" , "r" , stdin) ;
	scanf("%d%d%d%d%d" , &n , &k , &m , &c , &d) ;
	for ( int i = 1 ; i <= n ; i++ ) scanf("%lld" , &a[i]) ;
	ll l = 0 , r = INF ;
	while ( l < r )
	{
		ll mid = ( l + r + 1 ) >> 1 ;
//		printf("check:%lld %lld\n" , l ,r) ;
		if ( check( mid ) ) l = mid ;
		else r = mid - 1 ;
	}
	printf("%lld\n" , l) ;
	return 0 ;
}

D

在同一個人想去的城市 \(a_i\)\(b_i\) 間連一條無向邊。用 \(0\)\(1\) 記錄當前每個點的的奇偶性,這樣如果反轉一個人要去的城市,那麼對於 \(a_i\)\(b_i\) 的值,有幾種可能的變化:

\[(0,0)\rightarrow (1,1)\\ (1,1)\rightarrow (0,0)\\ (0,1)\rightarrow (1,0)\\ (1,0)\rightarrow (0,1)\\ \]

可以看出,要麼將兩個 \(0\) 或者 \(1\) 反轉,要麼將一個 \(0\)\(1\) 換位。我們希望最後 \(1\) 的數量儘量少,因此一個包含 \(x\)\(1\) 的連通塊最後至少(且可以)剩下 \(x\%2\)\(1\)

建圖進行DFS即可,複雜度為 \(O(n)\)

#include<cstdio>
const int N=2e5;
int f[N],s[N],m,n,k,x,y,i;
int _(int x){ return x==f[x]?x:f[x]=_(f[x]); }
int main(){
	scanf("%d%d%d",&m,&n,&k);
	for(i=1;i<=n;i++)f[i]=i;
	while(m--){
		scanf("%d%d",&x,&y);
		x=_(x);y=_(y);
		if(x^y)s[y]+=s[x],f[x]=y;
		++s[y];
	}
	for(i=1;i<=n;i++)
		k-=i==f[i]&&s[i]%2;
	printf("%d",k);
}