總結
第四次訓練成績感覺還行,但心態有點爆炸,首先,必須要改正有事沒事改個bug就交題的壞習慣,這會導致罰時爆炸,其次,算法最好想清楚再開打,不要打一點想一點,好險G是趕出來了,萬一幹不出來lct打那麼久就廢了。最後,我的隊友太強啦………………
A - Character Encoding (容斥+組合數學)
description
m個數,每個數取[0…n-1],問總和爲k的方案數(n,m,k<=1e5,T<=400,sum m<=5e6)
solution
若不考慮每個數的範圍限制,很容易知道方案就是,但現在有了範圍限制,我們考慮容斥一下枚舉>=n的數量變成單次詢問O(min(m,k/n))由於所有m加起來<5e6,所以過了
題外話,我之所以會算出這條式子後大眼瞪小眼想要給它降低複雜度10min,是因爲沒看到sum m<=5e6這個條件23333
code
#include<bits/stdc++.h>
#define ll long long
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(i,a,b) for(ll i=a;i>=b;i--)
using namespace std;
const ll maxn=2e5+5;
const ll mo=998244353;
ll c[maxn],ni[maxn];
ll n,m,i,t,j,k,l,x,y,z,p,T,r,mid,q;
ll ans,ans1;
ll mi(ll x,ll y){
if (y==1) return x;
ll t=mi(x,y/2);
if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
ll dg(ll x,ll y){
return c[x]*ni[y]%mo*ni[x-y]%mo;
}
int main(){
c[0]=1;
fo(i,1,maxn-5)c[i]=c[i-1]*i%mo;
t=maxn-5;ni[t]=mi(c[t],mo-2);
rp(i,t-1,0) ni[i]=ni[i+1]*(i+1)%mo;
scanf("%lld",&T);
while (T--){
scanf("%lld%lld%lld",&n,&m,&k);
if ((ll)(n - 1) * m < k) {
printf("0\n");
continue;
}
ans=0;t=k/n;p=-1;
fo(i,0,t){
p=-p;
ans+=p*dg(m,i)*dg(k-n*i-1+m,m-1)%mo;
}
ans=(ans%mo+mo)%mo;
printf("%lld\n",ans);
}
}
B - Pizza Hub (計算幾何)
description
把一個三角形放到一個指定寬度的矩形內,問矩形的最小高度
solution
歷史的經驗教訓表明,實數在0附近要控制精度如給一個1e-10的範圍
我們發現最小的情況一定可以轉化爲是三角形的一個頂點釘在矩形的一個頂點,另一個頂點釘在矩形的對邊(包含頂點),然後枚舉頂點討論下圖這兩種情況即可
code
#include<bits/stdc++.h>
#define ll long long
#define db double
#define max(a,b) ((a>b)?a:b)
#define min(a,b) ((a<b)?a:b)
#define fo(i,a,b) for(ll i=a;i<=b;i++)
#define rp(i,a,b) for(ll i=a;i>=b;i--)
using namespace std;
const ll maxn=2e5+5;
struct code{
db x,y;
}a[3];
int n,m,i,t,j,k,T;
db l,r,mid,x,y,z,w,b,c,d,p,s,mx,mi;
db sqr(db x){return x*x;}
db dg(int x,int y){return sqrt(sqr(a[x].x-a[y].x)+sqr(a[x].y-a[y].y));}
db dg1(int x,int y){return sqr(a[x].x-a[y].x)+sqr(a[x].y-a[y].y);}
db pan(int x,int y,int z){
db x2=a[y].x-a[x].x,x3=a[z].x-a[x].x,y2=a[y].y-a[x].y,y3=a[z].y-a[x].y;
db c1=(y2*y3+x2*x3)/sqrt(dg1(x,y)*dg1(x,z)),c2=w/dg(x,y);
db s1=sqrt(1-sqr(c1)),s2;
if (c2>1) c2=1,s2=0;
else s2=sqrt(1-sqr(c2));
db c3=c1*c2-s1*s2,s3=c1*s2+c2*s1,t=dg(x,z);
db first,second;
if (c3*t>w+1e-10 || c3*t<-1e-10)first=1e9;
else first=max(s3*t,s2*dg(x,y));
c3=c2*c1+s2*s1;s3=s2*c1-c2*s1;
if (s3<-1e-10 || c3*t>w+1e-10) second=1e9;
else second=s2*dg(x,y);
return min(first,second);
}
int main(){
scanf("%d",&T);
while (T--){
scanf("%lf%lf%lf%lf%lf%lf%lf",&a[0].x,&a[0].y,&a[1].x,&a[1].y,&a[2].x,&a[2].y,&w);
l=min(pan(0,1,2),pan(0,2,1));
l=min(l,min(pan(1,0,2),pan(1,2,0)));
l=min(l,min(pan(2,0,1),pan(2,1,0)));
l+=1e-12;
if (l<1e9)printf("%.9lf\n",l);
else printf("impossible\n");
}
}
C - Ringland (隊列)
description
在長度爲L的環上分別由N個黑點和白點,現要求黑白點兩兩配對,代價爲配對點距離的絕對值的和,求最小代價
solution
比賽時隊友看錯題整了超久,而我去和G作鬥爭了,快結束纔來看題,感覺是先算出開頭後枚舉環開始的位置然後搞搞就行了,但還是沒打出來。
首先我們知道,在不考慮跨環的情況下,一個白點肯定優先選擇前面的沒有配對的黑點,我們可以考慮依次配對,遇到白點+1,黑點-1,能配對就配對的原則進行算出在不跨環情況的最優答案。
我們還可以發現最優方案一定存在一個位置滿足從這個位置不被最優方案跨越,因此我們枚舉這個位置,計算每個位置的代價和sum取最小值。
那麼問題在於怎麼維護sum。
我們可以設每個黑點或白點x前方點的情況num(白點+1,黑點-1),將x的座標分別存入到d[num] (黑)和c[num](白)裏面,顯然答案就是維護這個c,d數組,每次位置右移到i+1時將顯然a[i].x(假設爲白點)在原來以開頭是減的,把它的影響消去,同時所有點的num都會減1,相當於零點右移,其他的和都不變,問題在於d[1]和c[0]由過去的加變成減了,現在要消去影響。
code
我打了個O(N)的代碼,結果T了,僅供參考
#include<bits/stdc++.h>
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const ll maxn=1e6+7;
struct code{
ll x,y;
}a[maxn];
ll b[maxn],d[maxn],c[maxn];
ll n,m,i,t,j,k,l,x,y,z,num,ans,sum,en,T;
int read(){
int f=1,s=0;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
return f*s;
}
int main(){
//freopen("data.in","r",stdin);
T=read();
while (T--){
n=read();m=read();
fo(i,1,n) b[i]=read();
j=1;num=0;
fo(i,1,n){
x=read();
while (b[j]<x && j<=n) a[++num].x=b[j++],a[num].y=1;
a[++num].x=x,a[num].y=0;
}
while (j<=n)a[++num].x=b[j++],a[num].y=1;
fo(i,1,2*n)d[i]=c[i]=0;
d[0]=c[0]=0;
sum=0;num=n;
fo(i,1,2*n)
if (a[i].y>0){
if (num<n) sum+=a[i].x;
else sum-=a[i].x;
d[num++]+=a[i].x;
}else{
if (num>n) sum+=a[i].x;
else sum-=a[i].x;
c[num--]+=a[i].x;
}
ans=sum;num=n;en=n;
fo(i,1,2*n){
sum+=a[i].x;
if (a[i].y>0){
d[num++]-=a[i].x;
sum+=2*d[num-1];
sum-=2*c[num];
sum+=a[i].x+m;
d[en++]+=a[i].x+m;
}else{
c[num--]-=a[i].x;
sum-=2*d[num];
sum+=2*c[num+1];
sum+=a[i].x+m;
c[en--]+=a[i].x+m;
}
ans=min(ans,sum);
}
printf("%lld\n",ans);
}
}
G - Variance-MST (LCT+kruscal)
description
給你一個圖,每條邊都有一個權值,要求選出一棵樹,要求這棵樹的標準差最小。(n<1e5,m<=2e5)
solution
我打完a就來幹這題,一想到lct+kruscal就開心地上了,結果最後就一直在幹這題,期間一度心態爆炸去看K,結果發現還是來調試G比較好23333
終於在最後趕出來了……
我們容易將方差轉化爲要維護一棵樹和的和,我們從小到大每次加入一條邊x後從環中間刪去一條最小的邊y,但這樣容易造成一個問題,因爲你求的是一個方差最小,有可能你刪去的y也是個比較大的邊,同樣可能存在一個x’>x,但刪去的y’<y,而且(x’+y’)/2<(x+y)/2,理論上應該優先被考慮,這樣就不滿足和的遞增的特點,所以我們可以搞兩次LCT,第一次記錄x+y的值後重新把邊按照x+y的優先級排序,再做一次LCT。
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define rep(i,a) for(i=first[a];i;i=next[i])
using namespace std;
const ll maxn=300007;
const ll mo=998244353;
ll i,j,k,l,m,x,y,u,v,T;
ll n,sum,num,sqr,ans,key[maxn],ans1,c[maxn];
ll t[maxn][2],f[maxn],fa[maxn],pfa[maxn],wei[maxn],lca,d[maxn];
bool bz[maxn];
double mi1,p,q;
struct node{
ll x,y,a,b;
}a[maxn];
bool cmp(node x,node y){
return x.a<y.a;
}
bool cmp1(node x,node y){
return x.b<y.b;
}
ll gf(ll x){
if(!fa[x])return x;
fa[x]=gf(fa[x]);return fa[x];
}
bool son(ll x){if(t[f[x]][0]==x)return 0;return 1;}
void update(ll x){
ll l=wei[t[x][0]],r=wei[t[x][1]];
if(key[l]<key[r])wei[x]=l;else wei[x]=r;
if(key[x]<key[l]&&key[x]<key[r])wei[x]=x;
}
void rotate(ll x){
ll y=f[x],z=son(x);
t[y][z]=t[x][1-z];
if(t[x][1-z])f[t[x][1-z]]=y;f[x]=f[y];
if(f[x])t[f[x]][son(y)]=x;else pfa[x]=pfa[y],pfa[y]=0;
t[x][1-z]=y;f[y]=x;
update(y);update(x);
}
void down(ll x){
if(bz[x]){
swap(t[x][0],t[x][1]);
if(t[x][0])bz[t[x][0]]^=1;if(t[x][1])bz[t[x][1]]^=1;
bz[x]=0;
}
}
void remove(ll x,ll y){
do{
d[++d[0]]=x;
x=f[x];
}while(x!=y);
while(d[0])down(d[d[0]--]);
}
void splay(ll x,ll y){
remove(x,y);
while(f[x]!=y){
if(f[f[x]]!=y)if(son(f[x])==son(x))rotate(f[x]);else rotate(x);
rotate(x);
}
}
void access(ll x){
ll y=0;
while(x){
splay(x,0);
f[t[x][1]]=0,pfa[t[x][1]]=x;
t[x][1]=y,f[y]=x;
pfa[y]=0;
update(x);
y=x,x=pfa[x];
}lca=y;
}
void makeroot(ll x){
access(x);
splay(x,0);
bz[x]^=1;
}
void link(ll x,ll y){
makeroot(x);
pfa[x]=y;
}
void cut(ll x,ll y){
makeroot(x);
access(y);
splay(y,0);
t[y][0]=0,f[x]=pfa[x]=0;
update(y);
}
ll find(ll x,ll y){
makeroot(x);
access(y);
splay(y,0);
return wei[y];
}
ll mi(ll x,ll y){
if (y==1) return x;
ll t=mi(x,y/2);
if (y%2) return t*t%mo*x%mo;return t*t%mo;
}
int main(){
scanf("%lld",&T);
while (T--){
scanf("%lld%lld",&n,&m);
fo(i,1,n)d[i]=0;
fo(i,1,m)
scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].a);
fo(i,0,m+n)fa[i]=pfa[i]=t[i][0]=t[i][1]=f[i]=bz[i]=wei[i]=0,key[i]=1e9;
sort(a+1,a+1+m,cmp);
fo(i,1,m)key[i+n]=a[i].a,wei[i+n]=i+n;
num=sum=sqr=0;
fo(i,1,m){
x=a[i].x,y=a[i].y;u=gf(x),v=gf(y);
if(u!=v){
fa[v]=u;
link(x,i+n);
link(y,i+n);
num++;
sum+=a[i].a;sqr+=a[i].a*a[i].a;
a[i].b=a[i].a-1e9;
}else{
ll o=find(x,y);
a[i].b=key[o]+a[i].a;
cut(a[o-n].x,o);
cut(a[o-n].y,o);
link(x,i+n);
link(y,i+n);
}
}
q=sqr;p=sum;
mi1=q-p/(n-1)*p;
ans=sqr%mo;ans1=sum%mo;
fo(i,0,m+n)fa[i]=pfa[i]=t[i][0]=t[i][1]=f[i]=bz[i]=wei[i]=0,key[i]=1e9;
sort(a+1,a+1+m,cmp1);
fo(i,1,m)key[i+n]=a[i].a,wei[i+n]=i+n;
num=sum=sqr=0;
fo(i,1,m){
x=a[i].x,y=a[i].y;u=gf(x),v=gf(y);
if(u!=v){
fa[v]=u;
link(x,i+n);
link(y,i+n);
num++;
sum+=a[i].a;sqr+=a[i].a*a[i].a;
}else{
ll o=find(x,y);
sum=sum-key[o]+a[i].a;
sqr=sqr-key[o]*key[o]+a[i].a*a[i].a;
cut(a[o-n].x,o);
cut(a[o-n].y,o);
link(x,i+n);
link(y,i+n);
q=sqr;p=sum;
if (num==n-1 && q-p/(n-1)*p<mi1){
mi1=q-p/(n-1)*p;
ans=sqr%mo;ans1=sum%mo;
}
}
}
ll ni=mi((n-1)%mo,mo-2);
ans=(ans-ans1*ans1%mo*ni%mo+mo)%mo*ni%mo;
printf("%lld\n",ans);
}
}
I - Werewolf (推理)
description
一個島上由兩種人:村民和狼人,村民必定說真話,狼人可能說假話,現在每個人都指認一個人的身份,問有分別有多少人必定是村民或狼人。
solution
我啥都不知道,口胡一下,矛盾當且僅當一條村名指認鏈後出現一個“村民”指認之前的一個“村民”是狼人。這樣就可以判斷出這個被指認是狼人的村民以及指認他是村民的人都是狼人。
code
#include<bits/stdc++.h>
typedef long long LL;
const int oo=0x3f3f3f3f;
const int N=1e5+10;
using namespace std;
int read(){
int f=1,s=0;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
return f*s;
}
int a[N],cnt[N],s[N];
bool b[N],vis[N];
int ans[2];
char c[10];
void search(int u,int w){
int v=a[u];
if(v!=w){
search(v,w);
}
if(s[v]!=oo)
if(s[v]!=b[u])s[u]=1;
}
void dfs(int u){
vis[u]=true;
cnt[u]+=b[u];
int v=a[u];
if(v==0)return;
if(vis[v]==true){
if(cnt[u]-cnt[v]+b[v]==1&&a[v]){
int x=v;
while(b[x]==0)x=a[x];
x=a[x];
s[x]=1;
search(a[x],x);
}
}else cnt[v]+=cnt[u],dfs(v);
if(s[v]!=oo)
if(s[v]!=b[u])s[u]=1;
if(s[u]!=oo)
ans[s[u]]++;
a[u]=0;
}
void work(){
int n=read();
for(int i=1;i<=n;i++){
a[i]=read();
scanf("%s",c);
b[i]=c[0]=='w';
}
memset(vis,0,sizeof(vis));
memset(s,0x3f,sizeof(s));
memset(cnt,0,sizeof(cnt));
ans[0]=ans[1]=0;
for(int i=1;i<=n;i++)
if(vis[i]==false)dfs(i);
printf("%d %d\n",ans[0],ans[1]);
}
int main(){
int T=read();
while(T--)work();
return 0;
}
L - Pinball (模擬)
description
給你一個斜面和小球的初位置,假設小球與斜面碰撞無能量損失,求與斜面碰撞次數
solution
模擬
code
#include<bits/stdc++.h>
typedef long long LL;
const int oo=0x3f3f3f3f;
using namespace std;
int read(){
int f=1,s=0;char c=getchar();
for(;c<'0'||c>'9';c=getchar())if(c=='-')f=-1;
for(;c>='0'&&c<='9';c=getchar())s=s*10+c-'0';
return f*s;
}
void work(){
double a=read(),b=read(),x=read(),y=read();
double jiao=atan(b*1.0/a);
double gx=9.8*sin(jiao),gy=9.8*cos(jiao);
double yy=abs(b*x+a*y)/sqrt(a*a+b*b);
double xx=(-x)/cos(jiao)+yy*tan(jiao);
double t=sqrt(2*xx/gx);
double t0=sqrt(2*yy/gy);
int ans=0;
if(t>t0){
t-=t0;
ans++;
ans+=t/t0/2;
}
printf("%d\n",ans);
}
int main(){
int T=read();
while(T--)work();
return 0;
}