【清北學堂刷題班】 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;
}