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\) 或者 \(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);
}