atcoder 雜題 #01

dengchengyu發表於2024-12-05

atcoder 雜題 #01

  • arc163_c Harmonic Mean
  • arc065_c Manhattan Compass
  • abc303_f Damage over Time
  • arc065_d Shuffling

arc163_c

可能因為數學不好,所以栽在了這道 Luogu 評的綠題上。

題目大意:求一個長為 \(n\) 的正整數序列 \(a\) 使得 \(\sum\frac 1 {a_i} =1\),要求 \(a_i\) 互不相同,且 \(a_i\le10^9\)

這題開始想著往通分後的 \(lcm\)\(n\) 個因數的和等於 \(lcm\) 方向考慮,發現並沒有什麼有趣的性質。

最終看了題解後知道,這道題要依靠這樣一個式子不斷拆解分數:

\[\frac 1 x=\frac 1 {x+1}+\frac 1{x(x+1)} \]

然後首先特判 \(n\le 2\) 的情況,\(n=1\) 就是 \(a_1=1\)\(n=2\) 就是無解。

然後考慮從 \(\frac 1 2,\frac 1 3,\frac 1 6\) 開始拆,把這些東西加入一個 set 裡,每次取出最小的數然後拆,這樣才能使最終序列的分母儘可能小,注意到可能拆完後與原來序列中的數有重複,比如 \(\frac12=\frac13+\frac16\),所以如果有重複就直接把它加入 \(a\) 陣列裡,以後不再拆它。

由於重複的很少,所以最後一定能達成 \(n\) 個,我們也可寫完後驗證可不可行。

AC 程式碼:

int T,n;
set<int> s;
const int N=1005;
int a[N];
signed main(){
	cin>>T;
	while(T--){
		cin>>n;
		if(n==1){
			cout<<"Yes\n1\n";
			continue;
		}
		if(n==2){
			cout<<"No\n";
			continue;
		}
		s={2,3,6};
		int tot=0;
		while(tot+s.size()<n){
			int x=*s.begin();
			if(s.find(x+1)!=s.end()||s.find((ll)(x+1)*x)!=s.end())a[++tot]=x,s.erase(s.begin());
			else{
				s.erase(s.begin());
				s.insert(x+1);
				s.insert((ll)x*(x+1));
			}
		}
		cout<<"Yes\n";
		fo(i,1,tot)cout<<a[i]<<" ";
		for(auto i:s)cout<<i<<" ";
	}
	return 0;
}

arc065_c

第一次自己想出來黑題!紀念一下。
(其實感覺這道題的思維難度並沒有黑題,程式碼也都是重複性的內容,並不難寫)。

題目大意:給定 \(n\) 個點,和初始無序點對 \((a,b)\),記 \(dis(a,b)\) 表示點 \(a,b\) 的曼哈頓距離,可以不斷操作,每次操作可以從一個無序點對 \((a,b)\) 移動到另一個滿足 \(dis(a,c)=dis(a,b)\) 的無序點對 \((a,c)\) 問你最終能到達多少個無序點對。

首先,套路地,把曼哈頓距離轉化為切比雪夫距離,把 \((x,y)\to (x+y,x-y)\)
實際上就是考慮到一個點曼哈頓距離相等的點,形成了一個傾斜 \(45\) 度的正方形。把正方形轉正,就從原來座標系下的 \((x,y)\) 變成了新座標系下的 \((x+y,x-y)\),曼哈頓就變成了切比雪夫距離。

切比雪夫距離就是對於兩個點 \((x,y),(x',y')\),距離為 \(\max(|x-x'|,|y-y'|)\)

回到題目上來,我們每次 \((a,b)\to(a,c)\) 實際上可以看作 \((b,a)\to(a,c)\)
我們只關心這個無序對的第二個點 \(a\),然後每次移動就是把無序對的第二個點換成滿足 \(dis(a,b)=L\)\(b\)\(L\) 為初始點對的距離。

然後我們能求出所有能出現在最終點對中的點,再對這些點統計答案。

這個問題可以 BFS 做,具體地,對每個 \(x'\)set 維護當前還沒有遍歷到的 \(x=x'\) 的點及其 \(y\), 對每個 \(y'\) 也這樣。
我們每次對 \(x=x_u-L\)set,二分位置取出 \(y_u-L\le y\le y_u+L\) 的點,然後把這些點加入佇列,對於 \(x=x_u+L,y=y_u-L,y=y_u+L\) 的也類似。

每當一個點加入佇列時,就把它對應 set 中的點刪掉,這樣就能保證每個遍歷到的點都是新的點,保證了複雜度。
每個點在 set 中遍歷 \(O(1)\) 次,且加入佇列更新其他點 1 次,所以這一部分的複雜度是 \(O(n\log n)\) 的。

求出所有能遍歷到的點後,距離其中一個點為 \(L\) 的點與這個點都能形成合法點對。
再用類似方法求出個數即可。把點扔到 \(x\)\(y\)vector 裡。二分位置,兩個位置相減求個數。注意正方形的四個角可能算重。

最後由於是無序對,答案需要除以 2。

AC 程式碼,AC 記錄

const int N=1e5+5;
int n,t1,t2;
int X[N],Y[N];
int qx[N],qy[N];
int bx[N],by[N];
int bz[N];
set<pair<int,int> > x1[N],y1[N];
vector<int> x2[N],y2[N];
signed main(){
	read(n,t1,t2);
	fo(i,1,n){
		int x,y; read(x,y);
		X[i]=x+y,Y[i]=x-y;
		bx[i]=X[i],by[i]=Y[i];
	}
	sort(bx+1,bx+1+n);
	sort(by+1,by+1+n);
	int len=unique(bx+1,bx+1+n)-bx-1;
	fo(i,1,n)qx[i]=lower_bound(bx+1,bx+1+len,X[i])-bx;
	int lx=len;
	len=unique(by+1,by+1+n)-by-1;
	fo(i,1,n)qy[i]=lower_bound(by+1,by+1+len,Y[i])-by;
	fo(i,1,n)x1[qx[i]].insert({Y[i],i}),y1[qy[i]].insert({X[i],i});	
	queue<int> q;
	q.push(t1),bz[t1]=1;
	x1[qx[t1]].erase({Y[t1],t1}),y1[qy[t1]].erase({X[t1],t1});
	q.push(t2),bz[t2]=1;
	x1[qx[t2]].erase({Y[t2],t2}),y1[qy[t2]].erase({X[t2],t2});
	int L=max(abs(X[t1]-X[t2]),abs(Y[t1]-Y[t2]));
	while(q.size()){
		int u=q.front(); q.pop();
		int l=lower_bound(bx+1,bx+1+lx,X[u]-L)-bx;
		vector<int> add;
		if(bx[l]==X[u]-L){
			auto p1=x1[l].lower_bound({Y[u]-L,0}),p2=x1[l].lower_bound({Y[u]+L+1,0});
			while(p1!=p2){
				add.push_back(p1->second);
				++p1;
			}
		}
		l=lower_bound(bx+1,bx+1+lx,X[u]+L)-bx;
		if(bx[l]==X[u]+L){
			auto p1=x1[l].lower_bound({Y[u]-L,0}),p2=x1[l].lower_bound({Y[u]+L+1,0});
			while(p1!=p2){
				add.push_back(p1->second);
				++p1;
			}
		}
		l=lower_bound(by+1,by+1+len,Y[u]-L)-by;
		if(by[l]==Y[u]-L){
			auto p1=y1[l].lower_bound({X[u]-L,0}),p2=y1[l].lower_bound({X[u]+L+1,0});
			while(p1!=p2){
				add.push_back(p1->second);
				++p1;
			}
		}
		l=lower_bound(by+1,by+1+len,Y[u]+L)-by;
		if(by[l]==Y[u]+L){
			auto p1=y1[l].lower_bound({X[u]-L,0}),p2=y1[l].lower_bound({X[u]+L+1,0});
			while(p1!=p2){
				add.push_back(p1->second);
				++p1;
			}
		}
		sort(add.begin(),add.end());
		add.resize(unique(add.begin(),add.end())-add.begin());
		for(auto i:add){
			bz[i]=1;
			q.push(i);
			x1[qx[i]].erase({Y[i],i}),y1[qy[i]].erase({X[i],i});
		}
	}
	fo(i,1,lx)x1[i].clear();
	fo(i,1,len)y1[i].clear();
	fo(i,1,n)x1[qx[i]].insert({Y[i],i}),y1[qy[i]].insert({X[i],i});	
	fo(i,1,lx)for(auto j:x1[i])x2[i].push_back(j.first);
	fo(i,1,len)for(auto j:y1[i])y2[i].push_back(j.first);
	ll ans=0;
	fo(i,1,n){
		if(bz[i]){
			int l=lower_bound(bx+1,bx+1+lx,X[i]-L)-bx;
			if(bx[l]==X[i]-L)ans+=upper_bound(x2[l].begin(),x2[l].end(),Y[i]+L-1)-lower_bound(x2[l].begin(),x2[l].end(),Y[i]-L);
			l=lower_bound(bx+1,bx+1+lx,X[i]+L)-bx;
			if(bx[l]==X[i]+L)ans+=upper_bound(x2[l].begin(),x2[l].end(),Y[i]+L)-lower_bound(x2[l].begin(),x2[l].end(),Y[i]-L+1);
			l=lower_bound(by+1,by+1+len,Y[i]-L)-by;
			if(by[l]==Y[i]-L)ans+=upper_bound(y2[l].begin(),y2[l].end(),X[i]+L)-lower_bound(y2[l].begin(),y2[l].end(),X[i]-L+1);
			l=lower_bound(by+1,by+1+len,Y[i]+L)-by;
			if(by[l]==Y[i]+L)ans+=upper_bound(y2[l].begin(),y2[l].end(),X[i]+L-1)-lower_bound(y2[l].begin(),y2[l].end(),X[i]-L);
		}
	}
	write(ans/2);
	return 0;
}

abc303_f

首先考慮二分答案,然後在時間 \(j\)\(i\) 方式攻擊的貢獻就確定了,我們要判斷以最優方式攻擊的貢獻和是否不小於 \(H\)

考慮把攻擊方式按 \(t_i\) 排序,我們依次確定時間為 \(t_i\sim t_{i+1}\) 這一段以最優方式攻擊的貢獻和。

那麼對於 \(j\le i\) 的攻擊方式,一個時間的貢獻就是 \(\max\{t_j\times d_j\}\)
而對於 \(j>i\) 的攻擊方式,時間 \(k\) 的貢獻就是 \(k\times \max\{d_j\}\)

在每個段,我們可以 \(O(1)\) 計算一個分界點,分界點前以 \(j\le i\) 的作為攻擊方式更優,分界點後以 \(j>i\) 的作為攻擊方式更優。
具體地,就是解

\[x\times \max_{j>i}\{d_j\}>\max_{j\le i}\{t_j\times d_j\} \]

細節:我們可以插入一個 \(t=1,d=0\) 的攻擊方式,可以方便實現。check 時需要注意特判 \(mid\) 的邊界,同時記得開 __int128

時間複雜度 \(O(n\log V)\)

AC 程式碼:

const int N=3e5+5;
ll h;
int n;
pair<int,int> a[N];
int mx[N];
int check(ll x){
	it128 sum=0;
	ll Max=0;
	fo(i,1,n){
		Max=max(Max,(ll)a[i].first*a[i].second);
		if(a[i].first>x)break;
		if(a[i].first!=a[i+1].first){
			if(i==n)sum+=(it128)(x-a[i].first+1)*Max;
			else {
				ll t=floor((ld)Max/mx[i+1]);
				if(t<a[i].first)t=a[i].first-1;
				if(t>=a[i+1].first)t=a[i+1].first-1;
				if(t>=x){
					sum+=(it128)(x-a[i].first+1)*Max;
					break;
				}
				sum+=(it128)(t-a[i].first+1)*Max;
				int up=a[i+1].first;
				if(up>x)up=x+1;
				sum+=(it128)(t+up)*(up-t-1)/2*mx[i+1];
			}
		}
	}
	return sum>=h;
}
signed main(){
	read(n,h);
	fo(i,1,n)read(a[i].first,a[i].second);
	++n;
	a[n]={1,0};
	sort(a+1,a+1+n);
	fd(i,n,1)mx[i]=max(mx[i+1],a[i].second);
	ll l=1,r=1e18,ans=0;
	while(l<=r){
		ll mid=(l+r)>>1;
		if(check(mid))ans=mid,r=mid-1;
		else l=mid+1;
	}
	write(ans);
	return 0;
}

arc065_d

不知道怎麼回事,十分鐘內秒掉了,感覺難度評高了(洛谷上評紫,kenkoooo 上評了橙色)。

這道題首先直接對每個位置填 0 或 1 計數,又由於 \(n\le 3000\) 所以考慮設 \(f_{i,j}\) 表示前 \(i\) 個填了 \(j\) 個 0 的方案數。

然後考慮怎麼限制,我們直接猜結論。
以 0 為例,如果我們要填第 \(j\) 個 0,那麼就要滿足前 \(i\) 個位置在 0 儘量多的情況下的 0 的個數要不小於 \(j\)
我們可以依次對每個 \([l,r]\) 的區間排序,就能把 0 儘可能往前放。

可以感性地感受到這個結論應該是正確的。

打完後,發現樣例過了,驗證了我們的猜想是正確的。

時間複雜度 \(O(nm\log n)\)

AC 程式碼也非常短:

const int N=3005;
char s[N],t[N];
int n,m;
int l[N],r[N];
int a[N],b[N];
int f[N][N];
const int mod=1e9+7;
signed main(){
	read(n,m),read(s+1);
	fo(i,1,m){
		read(l[i],r[i]);
	}
	fo(i,1,n)t[i]=s[i];
	fo(i,1,m){
		sort(s+l[i],s+r[i]+1);
		sort(t+l[i],t+r[i]+1,greater<>());
	}
	fo(i,1,n){
		a[i]=a[i-1],b[i]=b[i-1];
		a[i]+=s[i]=='0';
		b[i]+=t[i]=='1';
	}
	f[0][0]=1;
	fo(i,1,n){
		fo(j,0,i){
			if(j>0)(f[i][j]+=f[i-1][j-1]*(a[i]>=j))%=mod;
			(f[i][j]+=(f[i-1][j]*(b[i]>=i-j)))%=mod;
		}
	}
	int ans=0;
	fo(i,0,n)(ans+=f[n][i])%=mod;
	write(ans);
	return 0;
}

相關文章