1. 程式人生 > 實用技巧 >【清北學堂刷題班】 Day7

【清北學堂刷題班】 Day7

【清北學堂刷題班】 Day7

T1 water

題目描述

有一個長為n的序列\(a_i\),小Y希望數字總共不超過k種。

為了達到這個目的,她可以把任意位置修改成任意數,這個操作可以進行任意多次。

請你幫她求出最少需要修改幾個位置可以達成目的。

輸入格式

第一行兩個整數n,k。

接下來一個n個整數\(a_i\)

輸出格式

一行一個整數表示答案。

樣例輸入

5 2
2 2 1 1 5

樣例輸出

1

樣例解釋

把5(第5個位置的數)改成1或2就行了。

資料範圍

對於30%的資料,\(n\leq 20\)

對於另外30%的資料,\(n\leq 5000\)

對於所有資料,滿足\(q\le k \le n \le 200000,1\le a_i \le n\)

思路

這道目是真的水,這個題目統計一下每個數字的數量和數字的種類,然後按數量從小到大排序,刪除數量最小的\(k-k_{more}\)種就可以了

程式碼

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#define re register
#define ll long long
using namespace std;
const int maxn=2000005;
ll n,k;
ll kind,ans;
ll a[maxn],cnt[maxn];
bool v[maxn];
int main()
{
	cin>>n>>k;
	memset(cnt,0x3f,sizeof(cnt));
	memset(v,0,sizeof(v));
	ll maxn=-9999;
	for(re int i=1;i<=n;i++)
	{
		cin>>a[i];
		maxn=max(maxn,a[i]);
		if(!v[a[i]])
		{
			kind++;
			cnt[a[i]]=1;
			v[a[i]]=true;
		}
		else
		{
			cnt[a[i]]++;
		}
	}
	ll rev=kind-k;
	sort(cnt+1,cnt+maxn+1); 
	for(int i=1;i<=rev;i++)
	ans+=cnt[i];
	cout<<ans<<endl;
	return 0; 
}

T2 circle

題目描述

如果你聽過我今年的冬令營營員交流講課,那麼這將會是一道水題。

有一個\(1...n-1\)依次連成的環,有一個從1開始移動的指標,每次指標所在位置有p的概率消失並將這個位置對應的下標(在\(1...n-1\)中)插入序列B的末尾,然後指標移動一格(1移到2,n移到1這樣,一個位置若已經消失則不會被移動到)。所有位置都消失時遊戲結束。

最後B會是一個排列,你需要求出B的逆序對的期望,對998244353取模。

輸入格式

一行三個整數n,x,y。概率p=xy。

輸出格式

一行一個整數表示答案。

樣例輸入

4 1 2

樣例輸出

2

樣例解釋

因為一些原因,本題不提供樣例解釋。

資料範圍

對於30%的資料,\(n\le 10\)

對於40%的資料,\(n\le 15\)

對於50%的資料,\(n\le 20\)

對於所有資料,\(1\le n \le 10^8,0<x<y\le 10^9\)

思路

那麼只要做\(n=2\)的情況,答案是\(\sum_{t}q^{2t+1}p=\frac {qp} {1-q^2}\)
因此此題只需輸出\(\frac {\frac {pqn(n-1)} {2}} {1-q^2}\)即可

程式碼(std)

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define L(i,u) for (register int i=head[u]; i; i=nxt[i])
#define rep(i,a,b) for (register int i=(a); i<=(b); i++)
#define per(i,a,b) for (register int i=(a); i>=(b); i--)
using namespace std;
typedef long double ld;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> Pii;
typedef vector<int> Vi;
template<class T> inline void read(T &x){
	x=0; char c=getchar(); int f=1;
	while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}
	while (isdigit(c)) {x=x*10+c-'0'; c=getchar();} x*=f;
}
template<class T> T gcd(T a, T b){return !b?a:gcd(b,a%b);}
template<class T> inline void umin(T &x, T y){x=x<y?x:y;}
template<class T> inline void umax(T &x, T y){x=x>y?x:y;}
inline ui R() {
	static ui seed=416;
	return seed^=seed>>5,seed^=seed<<17,seed^=seed>>13;
}
const int mo = 998244353;
int power(int a, int n) {
	int rs=1;
	while(n){
		if(n&1)rs=1ll*rs*a%mo;
		a=1ll*a*a%mo;n>>=1;
	}
	return rs;
}
int n,A,B,p,q;
int main() {
	read(n);read(A);read(B);
	p=1ll*A*power(B,mo-2)%mo;
	q=1+mo-p;
	int res=(1ll*n*(n-1)/2%mo*p%mo*q%mo*power(1-1ll*q*q%mo+mo,mo-2)%mo+mo)%mo;
	printf("%d\n",res);
	return 0;
}

T3 path

題目描述

有一個n個點m條邊的DAG(有向無環圖),定義一條經過x條邊的路徑的權值為\(x^k\)

對於 \(i=1...n\), 求出所有 1 到 i 的所有路徑的權值之和, 對 998244353 取模。

輸入格式

第一行三個整數n,m,k。

接下來m行,每行兩個整數u,v,描述一條u到v的有向邊。

輸出格式

n行,每行一個整數表示答案。

樣例輸入

5 7 3
1 2
1 3
2 4
3 5
2 5
1 4
4 5

樣例輸出

0
1
1
9
51

資料範圍

對於20%的資料,\(n\le 2000,m\le 5000\)

對於另外10%的資料,\(k=1\)

對於另外20%的資料,\(k\le 30\)

對於所有資料,滿足\(n\le 100000,m\le 200000,0\le k\le 500\)

資料有梯度。

思路

這道題開始的時候我本來是想著從每條邊出發開始遍歷,然後沒經過一個點記錄深度,然後對於這個點的答案加上\(dep^k\),然後就可以了

但是真實的程式碼並不是這樣的,正解是:

先講暴力。 \(dp_{u,i}\)表示1到u的所有路徑邊數的i次方的和。答案就是\(dp_{u,k}\)
轉移就對於每條邊\(u\rightarrow v,dp_{v,i}+=\sum_{j}dp_{u,j} \begin{pmatrix} i\\j \end{pmatrix}\)
複雜度\(O(nk^2)\)
優化的話\(x^k=\sum_{i}\begin{Bmatrix} k\\i \end {Bmatrix} \begin{pmatrix} x\\i \end{pmatrix}\)
也就是說\(dp_{u,i}\)表示1到u的所有路徑邊數的i次下降冪的和,最後用 stirling 數還原出答案。
第2類stirling數可以遞推預處理,是經典問題。轉移就對於每條邊\(u\rightarrow v+=dp_{u,i-1}+dp_{u,i}\)
複雜度\(O(mk)\)

程式碼

#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<cassert>
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define per(i,a,b) for (int i=a; i>=b; i--)
typedef long long ll;
using namespace std;
typedef pair<int,int> Pii;
typedef vector<int> vec;
inline void read(int &x) {
	x=0; char c=getchar(); int f=1;
	while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}
	while (isdigit(c)) {x=x*10+c-'0'; c=getchar();} x*=f;
}
const int N = 202000, mo = 998244353;
const ll mod = (0x7fffffffffffffffLL / mo - mo)/503*mo;
ll dp[100005][502],s[505][505];
int n,m,K,q[N],deg[N];
int head[N],edgenum,nxt[N<<1],to[N<<1];
inline void add(int u, int v) {
	to[++edgenum]=v; nxt[edgenum]=head[u]; head[u]=edgenum; deg[v]++;
}
inline void topsort() {
	int f=1,r=1; rep(i,1,n) if (!deg[i]) q[r++]=i,dp[i][0]=1;
	while (f!=r) {
		int u=q[f++]; //printf("dot %d\n",u);
		for (int i=head[u]; i!=0; i=nxt[i]) {
			int v=to[i]; deg[v]--; if (!deg[v]) q[r++]=v;
			ll *p1=dp[u];
			ll *p2=dp[u];
			rep(k,0,K) {
				ll &tmp=dp[v][k];
				if (k==0) tmp+=(*p1);
				else {p1++; tmp+=(*p1)+(*p2)*k; p2++;}
			//	tmp+=dp[u][k]+(k==0?0:k*dp[u][k-1]);
				if (tmp>=mod) tmp%=mod;
			}
		}
	}
}
int main() {
	read(n); read(m); read(K);
	s[0][0]=1;
	rep(i,1,500) rep(j,1,i) s[i][j]=(s[i-1][j-1]+1LL*s[i-1][j]*j)%mo;
	rep(i,1,m) {int x,y; read(x); read(y); add(x,y);}
	topsort();
	rep(i,1,n) {
		ll ans=0; rep(j,0,K) ans+=s[K][j]*(dp[i][j]%mo)%mo;
		printf("%lld\n",ans%mo);
	}
	return 0;
}

T4 point

題目描述

你有n條直線,直線用\(A_ix+B_iy=C_i\)來表示。為了減少細節,保證這n條直線以及x軸、y軸兩兩都有恰好1個交點。

現在這n條直線兩兩的交點處產生了一個人,現在總共有n(n−1)/2個人。

你需要找到一個點P,使其距離所有人的曼哈頓距離和最小。若有多個滿足條件的點,選擇x座標最小的,若仍有多個,選擇y座標最小的。

輸入格式

第一行一個正整數n。

接下來n行,每行三個整數\(A_i,B_i,C_i\)

輸出格式

一行兩個實數表示答案,請保留6位小數輸出。

樣例輸入

3
1 1 1
2 -1 2
-1 2 2

樣例輸出

1.000000 1.000000

資料範圍

對於20%的資料,\(n\le 1000\)

對於40%的資料,\(n\le 5000\)

對於所有資料,滿足\(n\le 40000,|A_i|,|B_i|,|C_i|\le 10000\)

思路

我當時考試的時候想的是算出來每個點的座標,然後算出來曼哈頓距離,但是沒把程式碼打出來

正解:

答案的x座標即為所有人的中位數,y座標也同理。
二分答案,就是要數有幾個人在mid左側。
\(-\infty\)處按直線高度排序,那麼隨著x變大,直線會產生交點,也就是說直線高度大小關係會改變。
發現這個交點個數就是逆序對個數,這個可以畫圖理解。
樹狀陣列統計就好了\(O(n\log n \log V)\).

程式碼

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
#define ALL(x) x.begin(),x.end()
#define L(i,u) for (register int i=head[u]; i; i=nxt[i])
#define rep(i,a,b) for (register int i=(a); i<=(b); i++)
#define per(i,a,b) for (register int i=(a); i>=(b); i--)
using namespace std;
typedef long double ld;
typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> Pii;
typedef vector<int> Vi;
template<class T> inline void read(T &x){
	x=0; char c=getchar(); int f=1;
	while (!isdigit(c)) {if (c=='-') f=-1; c=getchar();}
	while (isdigit(c)) {x=x*10+c-'0'; c=getchar();} x*=f;
}
template<class T> T gcd(T a, T b){return !b?a:gcd(b,a%b);}
template<class T> inline void umin(T &x, T y){x=x<y?x:y;}
template<class T> inline void umax(T &x, T y){x=x>y?x:y;}
inline ui R() {
	static ui seed=416;
	return seed^=seed>>5,seed^=seed<<17,seed^=seed>>13;
}
const int N = 1e5+11;
int n,v[N];
struct line{int a,b,c;}s[N];
bool cmp(const line&x,const line&y){
	if(1ll*x.a*y.b!=1ll*x.b*y.a)return 1.0*x.a/x.b<1.0*y.a/y.b;
	return 1.0*x.c/x.b<1.0*y.c/y.b;
}
pair<double,int>f[N];
int qry(int p){int r=0;while(p)r+=v[p],p-=p&-p;return r;}
void mdy(int p){while(p<=n)v[p]++,p+=p&-p;}
double solve(){
	sort(s+1,s+n+1,cmp);
	double l=-2e8,r=2e8;
	rep(t,1,85){
		double mid=(l+r)/2;
		rep(i,1,n)f[i]=mp((s[i].c-mid*s[i].a)/s[i].b,i);
		sort(f+1,f+n+1);
		rep(i,1,n)v[i]=0;ll res=0;
		rep(i,1,n)res+=i-1-qry(f[i].se),mdy(f[i].se);
		if(res*2<n*(n-1ll)/2)l=mid;else r=mid;
	}
	return l;
}
int main() {
	read(n);
	rep(i,1,n)read(s[i].a),read(s[i].b),read(s[i].c);
	printf("%.6lf ",solve());
	rep(i,1,n)swap(s[i].a,s[i].b);
	printf("%.6lf\n",solve());
	return 0;
}