第十五屆藍橋杯大賽軟體賽省賽 C/C++ 大學 A 組

空気力学の詩發表於2024-04-16

Preface

答辯圈錢杯,去年省賽的題好歹還有些有意思的題(指杜教篩),今年變成煞筆題開會了是吧

兩個小時多一點就全寫完了,然後開始給後面三題寫對拍(結果發現其實都沒寫掛純在浪費時間)

考完感覺AK有望,結果後面發現最後一題漏看條件了,苦露西

而且中間EF兩題全偷懶開__int128了,反正用下發的編譯器開C++11是能跑的,也看的網上有其他人用了__int128,希望別CE的說


試題 A: 藝術與籃球

簽到,直接列舉判斷就好了,話說這種日期處理的相關題是藍橋杯特色環節嗎

答案應該是3228,但兩個填空題比賽時候的程式碼不知道為什麼被我弄沒了,沒法貼在這裡的說


試題 B: 五子棋對弈

還是大力列舉題,但要注意白子/黑子的數量應該是\(13/12\),看到好多錯的就是忘記判這個了

答案應該是3126376


試題 C: 訓練士兵

剛開始以為答案關於組團訓練的次數有三分性之類的,後面看了眼資料範圍原來可以直接列舉組團訓練的次數\(t\)

然後需要維護的就是\(c_i>t\)計程車兵的\(\sum c_i\times p_i\)\(\sum p_i\)的值了,拿個字尾和隨便維護一下

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int n,S,p,c,mx_c,spx[N],sfx[N];
signed main()
{
	RI i; for (scanf("%lld%lld",&n,&S),i=1;i<=n;++i)
	scanf("%lld%lld",&p,&c),mx_c=max(mx_c,c),spx[c]+=p,sfx[c]+=c*p;
	for (i=mx_c-1;i>=1;--i) spx[i]+=spx[i+1],sfx[i]+=sfx[i+1];
	int ans=sfx[1]; for (i=1;i<=mx_c;++i)
	ans=min(ans,i*S+(sfx[i]-spx[i]*i));
	return printf("%lld",ans),0;
}

試題 D: 團建

據說是個傻逼題,但有鑄幣做不來怎麼辦

沒關係直接大力Hash就完事,對兩棵樹的每個根到當前點的路徑做Hash,然後拿個map維護下匹配即可

寫了雙Hash應該也不會掛,自從用了這份模數後好像都沒被卡過Hash

#include<cstdio>
#include<iostream>
#include<vector>
#include<set>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod1=998244353,mod2=1e9+7;
int n,m,x,y,a[N],b[N],ans; vector <int> A[N],B[N];
struct Hasher
{
	int x,y;
	inline Hasher(CI X=0,CI Y=0)
	{
		x=X; y=Y;
	}
	friend inline bool operator < (const Hasher& A,const Hasher& B)
	{
		return A.x!=B.x?A.x<B.x:A.y<B.y;
	}
	friend inline Hasher operator + (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
	}
	friend inline Hasher operator - (const Hasher& A,const Hasher& B)
	{
		return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
	}
	friend inline Hasher operator * (const Hasher& A,const Hasher& B)
	{
		return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
	}
}; set <Hasher> rst;
const Hasher seed=Hasher(31,131);
inline void DFS1(CI now=1,CI fa=0,Hasher pre=Hasher())
{
	pre=pre*seed+Hasher(a[now],a[now]); rst.insert(pre);
	for (auto to:A[now]) if (to!=fa) DFS1(to,now,pre);
}
inline void DFS2(CI now=1,CI fa=0,CI dep=1,Hasher pre=Hasher())
{
	pre=pre*seed+Hasher(b[now],b[now]);
	if (rst.count(pre)) ans=max(ans,dep);
	for (auto to:B[now]) if (to!=fa) DFS2(to,now,dep+1,pre);
}
int main()
{
	RI i; scanf("%d%d",&n,&m);
	for (i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<=m;++i) scanf("%d",&b[i]);
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),A[x].push_back(y),A[y].push_back(x);
	for (i=1;i<m;++i) scanf("%d%d",&x,&y),B[x].push_back(y),B[y].push_back(x);
	return DFS1(),DFS2(),printf("%d",ans),0;
}

試題 E: 成績統計

首先一眼二分答案\(x\),考慮如何check(x)

不難發現對於\(1\sim x\)這段字首,先將所有數排個序,然後每次選相鄰的\(k\)個一定是最優的

但計算方差的時候顯然直接按照定義來不好處理,我們用\(D(x)=E(X^2)-[E(x)]^2\)轉化一下就很容易用字首和處理了

但要注意把比較的時候分母全乘到分子上時會爆long long,需要開__int128

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,T,a[N],b[N]; long long pfx[N],pfx2[N];
inline bool check(CI x)
{
	RI i; for (i=1;i<=x;++i) b[i]=a[i]; sort(b+1,b+x+1);
	for (i=1;i<=x;++i) pfx[i]=pfx[i-1]+b[i],pfx2[i]=pfx2[i-1]+1LL*b[i]*b[i];
	for (i=k;i<=x;++i)
	{
		long long sum=pfx[i]-pfx[i-k],sum2=pfx2[i]-pfx2[i-k];
		if ((__int128)(sum2)*k-(__int128)(sum)*sum<(__int128)(T)*k*k) return 1;
	}
	return 0;
}
int main()
{
	RI i; for (scanf("%d%d%d",&n,&k,&T),i=1;i<=n;++i) scanf("%d",&a[i]);
	int l=k,r=n,mid,ret=-1; while (l<=r)
	if (check(mid=l+r>>1)) ret=mid,r=mid-1; else l=mid+1;
	return printf("%d",ret),0;
}

試題 F: 因數計數

感覺算是本場最難的一個題了,我當時是看一眼覺得有點煩然後直接跳了,寫完後面兩個一眼題後回頭寫的

剛開始的做法是考慮確定四元組\((i,j,k,l)\)中的前兩項再計數,但感覺要討論很多並且複雜度也不好最佳化

後面考慮先列舉確定\(i,k\),因為它們有倍數關係所以直接列舉複雜度是\(O(n\log n)\)的(假設\(n\)和值域同階)

然後我們要考慮的就是當少了確定的這兩個數後,剩下的有倍數關係的數對的數量

這個很容易想到容斥計算,用初始時全域性的倍數關係數對的數量減去\(i,k\)各自帶來的影響即可

注意需要分\(i\)是否等於\(k\)兩種情況來討論計算,程式碼還是很好寫的

(PS:最後如果所有數都相同答案就是\(A_n^4\),會爆long long,因此又要開__int128

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,m,x,c[N],f[N],all; __int128 ans;
inline void write(__int128 x)
{
	if (x==0) return (void)(putchar('0'));
	if (x>9) write(x/10); putchar(x%10+'0');
}
signed main()
{
	//freopen("F.in","r",stdin); freopen("F.out","w",stdout);
	RI i,j; for (scanf("%lld",&n),i=1;i<=n;++i)
	scanf("%lld",&x),++c[x],m=max(m,x);
	for (i=1;i<=m;++i) if (c[i])
	for (j=i*2;j<=m;j+=i) if (c[j])
	f[i]+=c[j],f[j]+=c[i],all+=c[i]*c[j];
	for (i=1;i<=m;++i) all+=c[i]*(c[i]-1);
	for (i=1;i<=m;++i) if (c[i])
	{
		auto delta=[&](CI x,CI d)
		{
			return x*(x-1)-(x-d)*(x-d-1);
		};
		if (c[i]>=2) ans+=(__int128)(c[i])*(c[i]-1)*(all-delta(c[i],2)-f[i]*2);
		for (j=i*2;j<=m;j+=i) if (c[j]) ans+=(__int128)(c[i])*c[j]*(all-delta(c[i],1)-delta(c[j],1)-f[i]-f[j]+1);
	}
	return write(ans),0;
}

試題 G: 零食採購

這TM絕對是很久之前的NOIP原題了,我感覺我以前肯定做過,而且顏色數\(20\)也是很經典了

剛開始看完題以為要寫樹上莫隊了,感覺這東西討論起來有點小複雜,很久沒寫都快忘了

後面一看資料範圍\(c_i\le 20\)直接樂了,直接把每種顏色狀壓然後合併的時候或一下即可

本來以為要寫樹剖的以為又要被板子題腐乳了,後面一想媽的又沒有修改直接寫個倍增完事

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,q,c[N],x,y,cnt[(1<<20)+5]; vector <int> v[N];
int anc[N][20],col[N][20],dep[N];
inline void DFS(CI now=1,CI fa=0)
{
	dep[now]=dep[fa]+1; anc[now][0]=fa; col[now][0]=(1<<c[now]);
	for (RI i=0;i<19;++i) if (anc[now][i])
	anc[now][i+1]=anc[anc[now][i]][i],col[now][i+1]=col[now][i]|col[anc[now][i]][i]; else break;
	for (auto to:v[now]) if (to!=fa) DFS(to,now);
}
inline int getLCA(int x,int y)
{
	RI i; if (dep[x]<dep[y]) swap(x,y);
	for (i=19;i>=0;--i) if (dep[anc[x][i]]>=dep[y]) x=anc[x][i];
	if (x==y) return x;
	for (i=19;i>=0;--i) if (anc[x][i]!=anc[y][i]) x=anc[x][i],y=anc[y][i];
	return anc[x][0];
}
inline int getcol(int x,CI y)
{
	int ret=0; for (RI i=19;i>=0;--i)
	if (dep[anc[x][i]]>=dep[y]) ret|=col[x][i],x=anc[x][i];
	return ret|(1<<c[y]);
}
int main()
{
	//freopen("G.in","r",stdin); freopen("G.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&q),i=1;i<=n;++i) scanf("%d",&c[i]),--c[i];
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for (i=1;i<(1<<20);++i) cnt[i]=cnt[i>>1]+(i&1);
	for (DFS(),i=1;i<=q;++i)
	{
		scanf("%d%d",&x,&y); int z=getLCA(x,y);
		printf("%d\n",cnt[getcol(x,z)|getcol(y,z)]);
	}
	return 0;
}

試題 H: 封印寶石

這種字典序最大/最小的題目都是一套流程,按位列舉然後貪心選,這題也不例外

從左往右列舉當前位置\(i\),考慮能放入當前盒子的寶石範圍為\([i,i+k]\),那麼顯然從裡面挑一個最大的即可

如果有多個最大的那麼顯然先拿靠左的一定更優,然後隨便寫個線段樹維護下就行了

結果賽後才發現有個“相鄰的兩個盒子的寶石不能相同”,那就再額外維護個嚴格次大值以及其最左的位置即可

(PS:本題程式碼在賽後改過,加上了找次大值的部分,但很奇怪在民間資料的dashOJ上還是無法透過,合理推測是造的資料鍋了,因為除了admin好像沒一個過的)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,k,a[N],ans[N];
struct ifo
{
	int mx,mxp,smx,smxp;
	inline ifo(CI MX=-1e9,CI MXP=0,CI SMX=-2e9,CI SMXP=0)
	{
		mx=MX; mxp=MXP; smx=SMX; smxp=SMXP;
	}
};
class Segment_Tree
{
	private:
		ifo O[N<<2];
		inline ifo merge(const ifo& A,const ifo& B)
		{
			ifo C; if (A.mx>B.mx)
			{
				C.mx=A.mx; C.mxp=A.mxp;
				if (A.smx>=B.mx) C.smx=A.smx,C.smxp=A.smxp;
				else C.smx=B.mx,C.smxp=B.mxp;
			} else if (A.mx<B.mx)
			{
				C.mx=B.mx; C.mxp=B.mxp;
				if (A.mx>=B.smx) C.smx=A.mx,C.smxp=A.mxp;
				else C.smx=B.smx,C.smxp=B.smxp;
			} else
			{
				C.mx=A.mx; C.mxp=A.mxp;
				if (A.smx>=B.smx) C.smx=A.smx,C.smxp=A.smxp;
				else C.smx=B.smx,C.smxp=B.smxp;
			}
			return C;
		}
	public:
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void build(TN)
		{
			if (l==r) return (void)(O[now]=ifo(a[l],l));
			int mid=l+r>>1; build(LS); build(RS);
			O[now]=merge(O[now<<1],O[now<<1|1]);
		}
		inline ifo query(CI beg,CI end,TN)
		{
			if (beg<=l&&r<=end) return O[now]; int mid=l+r>>1; ifo ret;
			if (beg<=mid) ret=merge(ret,query(beg,end,LS));
			if (end>mid) ret=merge(ret,query(beg,end,RS)); return ret;
		}
		inline void updata(CI pos,TN)
		{
			if (l==r) return (void)(O[now]=ifo(-1,l)); int mid=l+r>>1;
			if (pos<=mid) updata(pos,LS); else updata(pos,RS);
			O[now]=merge(O[now<<1],O[now<<1|1]);
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
int main()
{
	//freopen("H.in","r",stdin); freopen("H.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&k),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (SEG.build(),ans[0]=-1,i=1;i<=n;++i)
	{
		int pos=SEG.query(i,min(n,i+k)).mxp;
		if (a[pos]==-1) { ans[i]=-1; continue; }
		if (ans[i-1]==-1||a[pos]!=ans[i-1])
		{
			ans[i]=a[pos]; a[pos]=-1;
			k-=(pos-i); SEG.updata(pos);
		} else
		{
			pos=SEG.query(i,min(n,i+k)).smxp;
			if (a[pos]==-1) { ans[i]=-1; continue; }
			ans[i]=a[pos]; a[pos]=-1;
			k-=(pos-i); SEG.updata(pos);
		}
	}
	for (i=1;i<=n;++i) printf("%d%c",ans[i]," \n"[i==n]);
	return 0;
}

Postscript

感覺今年只要EF不要因為__int128CE還是隨便拿省一的說

但不管怎麼說既然來打圈錢杯了目標肯定就是國一了,去年溝槽的國二第三名真是太難受了

相關文章