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不要因為__int128
CE還是隨便拿省一的說
但不管怎麼說既然來打圈錢杯了目標肯定就是國一了,去年溝槽的國二第三名真是太難受了