2019 ICPC Asia-East Continent Final C. Dirichlet k-th root
定義 爲狄利克雷卷積,,給出 ,,,求
,保證
題解:
結論:對於 ,
所以 , 是 在 下的逆元。
證明:
對於 ,,那麼記 的 的個數爲 ,選出這種組合的方案數是 ,而對於 ,。
對於 ,
複雜度
Code:
#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
const int mod = 998244353;
int n,k,f[maxn],g[maxn];
int Pow(int a,int b){int s=1;for(;b;b>>=1,a=1ll*a*a%mod) b&1&&(s=1ll*s*a%mod);return s;}
void Mul(int *A,int *B,int *c){
static int a[maxn],b[maxn];
memcpy(a,A,(n+1)<<2),memcpy(b,B,(n+1)<<2),memset(c,0,(n+1)<<2);
for(int i=1;i<=n;i++) for(int j=1;i*j<=n;j++) c[i*j]=(c[i*j]+1ll*a[i]*b[j])%mod;
}
void Pow(int *a,int *s,int b){
s[1]=1;
for(;b;b>>=1,Mul(a,a,a)) if(b&1) Mul(a,s,s);
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%d",&g[i]);
Pow(g,f,Pow(k,mod-2));
for(int i=1;i<=n;i++) printf("%d%c",f[i],i==n?10:32);
}
然而此題還有更優的解法。
2018 ACM-ICPC World Finals H. Single Cut of Failure
一個 的矩形,給出一些端點在邊上(不在交點且各不相同且不與矩形邊重合)的線段,求最少用幾條同樣條件的線段可以切割所有給出的線段。輸出方案。
每條線都與主對角線和副對角線中的某一條相交,所以答案最多爲2,輸出方案時偏離0.5即可。
判定是否能只用1條:將矩形看成排成一排的四條線段,給出的線段看做一些區間,相當於找到一個區間包含個屬於不同線段的端點,two-pointer掃描即可。
因爲不能讓區間在矩形的同一邊,所以右端點要儘量靠右,同時末端要加上一個點。
Code:
#include<bits/stdc++.h>
#define maxn 1000005
using namespace std;
int n,W,H;
bool vis[maxn];
struct node{
int x,id;
bool operator < (const node &p)const{return x<p.x;}
}a[maxn*2];
int turn(int x,int y){
return y==0?x:x==W?W+y:y==H?2*W-x+H:2*W+2*H-y;
}
void write(double x){
if(x<W) printf("%.1f %d",x,0);
else if(x<W+H) printf("%d %.1f",W,x-W);
else if(x<2*W+H) printf("%.1f %d",2*W+H-x,H);
else printf("%d %.1f",0,2*W+2*H-x);
}
void solve(){
for(int s=1,t=1;t<=2*n;s++){
while(t<=2*n&&!vis[a[t].id]) vis[a[t].id]=1,t++;
if(t-s==n) puts("1"),write(a[s].x-0.5),putchar(' '),write(a[t].x-0.5),exit(0);
vis[a[s].id]=0;
}
}
int main()
{
scanf("%d%d%d",&n,&W,&H);
for(int i=1,x,y;i<=n;i++){
scanf("%d%d",&x,&y),a[i*2-1]=(node){turn(x,y),i};
scanf("%d%d",&x,&y),a[i*2]=(node){turn(x,y),i};
}
a[2*n+1].x=2*W+2*H;
sort(a+1,a+1+2*n);
solve();
puts("2");
printf("%.1f %d %.1f %d\n",0.5,0,W-0.5,H);
printf("%d %.1f %d %.1f\n",W,0.5,0,H-0.5);
}
2018 ACM-ICPC World Finals I. Triangles
給出一個醬紫的圖,數三角形個數。
先數型,反過來同理。
數以每個點爲右下角的 個數,記錄每個點往左 ,往左上 ,往右上 ,能延伸多長。
對每一行從左往右掃,在處加1,掃到時將處的1減去,求中爲1的個數。
一條右上的斜線相當於“覆蓋了”它往右的一部分點,可以對它們做貢獻。
Code:
#include<bits/stdc++.h>
#define maxn 6005
#define maxm 12005
#define LL long long
using namespace std;
int n,m,U[maxn][maxm],D[maxn][maxm];
char a[maxn*2][maxm*2];
vector<int>G[maxm];
int arr[maxm];
void upd(int i,int v){for(;i;i-=i&-i) arr[i]+=v;}
int qsum(int i){int s=0;for(int lim=(m+1)/2;i<=lim;i+=i&-i) s+=arr[i];return s;}
int main()
{
freopen("1.in","r",stdin);
scanf("%d%d\n",&n,&m);
for(int i=1;i<=2*n-1;i++) fgets(a[i]+1,2*m+1,stdin);
LL ans=0;
for(int i=1;i<=2*n-1;i+=2){
int Mx=0;
for(int j=i/2&1?3:1,L=0,k=1;j<=2*m-1;j+=4,k++){
U[i][j] = a[i-1][j-1]=='\\' ? U[i-2][j-2]+1 : 0;
D[i][j] = a[i-1][j+1]=='/' ? D[i-2][j+2]+1 : 0;
L = a[i][j-1]=='-' ? L+1 : 0;
ans += qsum(k-min(U[i][j],L));
upd(k,1),G[k+D[i][j]].push_back(k),Mx=max(Mx,k+D[i][j]);
while(!G[k].empty()) upd(G[k].back(),-1),G[k].pop_back();
}
for(int k=m/2+1;k<=Mx;k++) while(!G[k].empty()) upd(G[k].back(),-1),G[k].pop_back();
}
for(int i=2*n-1;i>=1;i-=2){
int Mx=0;
for(int j=i/2&1?3:1,L=0,k=1;j<=2*m-1;j+=4,k++){
U[i][j] = a[i+1][j-1]=='/' ? U[i+2][j-2]+1 : 0;
D[i][j] = a[i+1][j+1]=='\\' ? D[i+2][j+2]+1 : 0;
L = a[i][j-1]=='-' ? L+1 : 0;
ans += qsum(k-min(U[i][j],L));
upd(k,1),G[k+D[i][j]].push_back(k),Mx=max(Mx,k+D[i][j]);
while(!G[k].empty()) upd(G[k].back(),-1),G[k].pop_back();
}
for(int k=m/2+1;k<=Mx;k++) while(!G[k].empty()) upd(G[k].back(),-1),G[k].pop_back();
}
printf("%lld\n",ans);
}
2018 ACM-ICPC World Finals C. Conquer the World
樹上每個位置有一些軍隊,給出每個位置需要的軍隊數量,有邊權,求移動軍隊使得滿足條件的最小代價。
費用流最小權匹配,樹上轉化爲模擬費用流的配對過程,用堆維護,詳細題解見 cz_xuyixuan‘s blog
正確性:同一棵子樹裏面的對如果和小於0那麼必然有一個會被選,所以提供反悔可能之後就可以提前選。
Code:
#include<bits/stdc++.h>
#define maxn 250005
#define maxp maxn*20
#define LL long long
using namespace std;
char cb[1<<20],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<20,stdin),cs==ct)?0:*cs++)
void read(int &a){
char c;while(!isdigit(c=getc()));
for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const LL inf = maxn*1e6;
int n,X[maxn],Y[maxn];
LL ans,dep[maxn];
int fir[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],tot;
void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
struct node{
LL v; int s;
}a[maxp];
int rt0[maxn],rt1[maxn],lc[maxp],rc[maxp],sz,dis[maxp];
int newnode(node p){return a[++sz]=p,sz;}
void merge(int &i,int x,int y){
if(!x||!y) return void(i=x+y);
if(a[y].v<a[x].v) swap(x,y);
i=x,merge(rc[i],rc[x],y);
if(dis[lc[i]]<dis[rc[i]]) swap(lc[i],rc[i]);
dis[i]=dis[rc[i]]+1;
}
void Push(int &i,int x){merge(i,i,x);}
void Pop(int &i){merge(i,lc[i],rc[i]);}
void solve(int u,int v,LL d){
for(;rt0[u]&&rt1[v];){
LL x=a[rt0[u]].v,y=a[rt1[v]].v;
if((x+y-2*d)>=0) return;
int s=min(a[rt0[u]].s,a[rt1[v]].s);
ans+=(x+y-2*d)*s;
if(!(a[rt0[u]].s-=s)) Pop(rt0[u]);
if(!(a[rt1[v]].s-=s)) Pop(rt1[v]);
Push(rt0[u],newnode((node){2*d-y,s}));
Push(rt1[v],newnode((node){2*d-x,s}));
}
}
void dfs(int u,int ff){
if(X[u]>0) Push(rt0[u],newnode((node){dep[u],X[u]}));
if(Y[u]>0) Push(rt1[u],newnode((node){dep[u]-inf,Y[u]})),ans+=Y[u]*inf;
for(int i=fir[u],v;i;i=nxt[i]) if((v=to[i])!=ff){
dep[v]=dep[u]+w[i],dfs(v,u),solve(u,v,dep[u]),solve(v,u,dep[u]);
merge(rt0[u],rt0[u],rt0[v]),merge(rt1[u],rt1[u],rt1[v]);
}
}
int main()
{
freopen("1.in","r",stdin);
read(n);
for(int i=1,x,y,z;i<n;i++) read(x),read(y),read(z),line(x,y,z),line(y,x,z);
for(int i=1,t;i<=n;i++) read(X[i]),read(Y[i]),t=min(X[i],Y[i]),X[i]-=t,Y[i]-=t;
dfs(1,0);
printf("%lld\n",ans);
}
2018 ACM-ICPC World Finals D. Gem island
個人,每個人初始有1個寶石,每過一天,在已有的寶石中等概率會有某個寶石變成兩個,問 天后擁有寶石數前 大的人擁有的寶石數的和的期望。
去掉最開始的那一顆,假設每個人最後多獲得了 顆寶石,,考慮這種情況發生的概率:
因爲每天每個寶石分裂的概率是相同的,所以總情況數爲
最後寶石的分佈列是 ,考慮 天中每天哪個人的寶石分裂,可重排列方案數是 ,然後每個人的寶石分裂的情況數是 ,總情況數爲
所以分佈列爲 的概率爲 ,與具體的 分佈無關。
因爲 是定值,所以最終每種分佈列的概率都是相同的,我們只需要算出每種分佈列的前 大之和累加之後除以總的分佈方案數就可以了。
設 表示給 個人分配 個寶石的方案數, 表示給 個人分配 個寶石的所有方案中前 大之和的和。
因爲要求前 大之和,所以我們DP的方式爲“給一些數整體+1",具體的,枚舉給 個數+1,表示這個數比其它的 個數大:
Code:
#include<bits/stdc++.h>
#define maxn 505
using namespace std;
int n,d,r;
double cnt[maxn][maxn],sum[maxn][maxn],c[maxn][maxn];
int main()
{
scanf("%d%d%d",&n,&d,&r);
for(int i=0,lim=max(n,d);i<=lim;i++)
for(int j=(c[i][0]=1);j<=i;j++)
c[i][j]=c[i-1][j-1]+c[i-1][j];
cnt[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=d;j++)
for(int k=0;k<=i&&k<=j;k++){
cnt[i][j]+=c[i][k]*cnt[k][j-k];
sum[i][j]+=c[i][k]*(sum[k][j-k]+min(k,r)*cnt[k][j-k]);
}
printf("%.8f\n",sum[n][d]/cnt[n][d]+r);
}
2018 ACM-ICPC World Finals E. Getting a Jump on Crime
一個 的網格圖,每個格子的是底面爲 ,高度爲 的長方體,給出速度大小 ,可以朝任何方向。起點在 的方格的中心,問到達每個格子最少跳幾次,跳的拋物線不能穿過任何一個格子,重力加速度 。
思路比較簡單,就是BFS,然後檢驗兩個點能否到達。
因爲橫縱座標之差和速度都是定的,所以拋物線可以解出來:
將用表示代入3式:
越大,越小,越大,拋物線曲線越高,所以解出 的最大值。
然後枚舉橫座標,找到豎着的邊界及其兩邊對應格子,算出穿過這個邊界時的 即可,橫着的邊界同理。
Code:
#include<bits/stdc++.h>
#define maxn 25
using namespace std;
const double G = 9.80665;
int n,m,sx,sy,dis[maxn][maxn];
double W,V,h[maxn][maxn];
double sqr(double x){return x*x;}
bool check(int x,int y,int tx,int ty){
double d = W*sqrt(sqr(tx-x)+sqr(ty-y)), A = sqr(G)/4, B = (G*(h[tx][ty]-h[x][y])-V*V), C = sqr(d)+sqr(h[tx][ty]-h[x][y]);
if(B*B-4*A*C<0) return 0;
double t = sqrt((-B+sqrt(B*B-4*A*C))/(2*A));
double xv = (tx-x)/t, yv = (ty-y)/t, vx = d/t, vy = sqrt(V*V-vx*vx);
for(int i=min(tx,x);i<max(tx,x);i++){// -|-
t = (i-x+0.5)/xv;
double fy = h[x][y]+vy*t-G*t*t/2; int p = ceil(y-0.5+t*yv);
if(max(h[i][p],h[i+1][p])>=fy) return 0;
if(y-0.5+t*yv==p&&max(h[i][p+1],h[i+1][p+1])>=fy) return 0;
}
for(int i=min(ty,y);i<max(ty,y);i++){
t = (i-y+0.5)/yv;
double fy = h[x][y]+vy*t-G*t*t/2; int p = ceil(x-0.5+t*xv);
if(max(h[p][i],h[p][i+1])>=fy) return 0;
if(x-0.5+t*xv==p&&max(h[p+1][i],h[p+1][i+1])>=fy) return 0;
}
return 1;
}
int main()
{
scanf("%d%d%lf%lf%d%d",&n,&m,&W,&V,&sx,&sy);
for(int i=1;i<=m;i++) for(int j=1;j<=n;j++) scanf("%lf",&h[j][i]);
memset(dis,-1,sizeof dis);
static int q[maxn*maxn],l,r; q[l=r=1]=(sy-1)*n+sx,dis[sx][sy]=0;
while(l<=r){
int y=(q[l]-1)/n+1,x=(q[l]-1)%n+1; l++;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(dis[i][j]==-1&&check(x,y,i,j))
q[++r]=(j-1)*n+i,dis[i][j]=dis[x][y]+1;
}
for(int i=1;i<=m;i++) for(int j=1;j<=n;j++)
if(~dis[j][i]) printf("%d%c",dis[j][i],j==n?10:32);
else printf("X%c",j==n?10:32);
}
2019 ICPC Asia-East Continent Final B. Black and White
的網格,已黑白相間染色,左下角爲白色,求從往右/上走到的輪廓線的左側白格子減黑格子=k的方案數。
考慮統計當前輪廓線上方白格子減去黑格子的和。
每次可以往右走或往上走,和不變/+1/-1。
爲了解決+1/-1,我們一次走兩步(需要 ),如果方向相同,那麼對和以及分佈都是沒有影響的;如果方向不同,假設全都是先右再上,走了 步,那麼最後的和等於 (奇數行白色多1,偶數行黑白相等),如果將其中的 步替換爲先上再右,相當於白格子少了,和爲
然後枚舉 組合數計算方案數即可。
如果 ,那麼枚舉最後一步往右還是往上,劃歸爲 的情況。
Code:
#include<bits/stdc++.h>
#define maxn 100005
using namespace std;
const int mod = 998244353;
int fac[maxn],inv[maxn];
int f(int n,int m,int k){
int ret=0,x=(n+m)/2;
for(int p=n&1,lim=min(n,m);p<=lim;p+=2){
int q=(p+1)/2-k;
if(0<=q&&q<=p) ret=(ret+1ll*fac[x]*inv[(n-p)>>1]%mod*inv[(m-p)>>1]%mod*inv[q]%mod*inv[p-q])%mod;
}
return ret;
}
int main()
{
fac[0]=fac[1]=inv[0]=inv[1]=1;
for(int i=2;i<maxn;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<maxn;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
int T,n,m,k;
for(scanf("%d",&T);T--;){
scanf("%d%d%d",&n,&m,&k);
if((n+m)&1) printf("%d\n",(f(n,m-1,k)+f(n-1,m,k+(m&1)))%mod);
else printf("%d\n",f(n,m,k));
}
}
2018 ACM-ICPC World Finals G. Panda Preserve
簡單多邊形,求最小半徑 ,使得以多邊形頂點爲圓心的半徑爲 的所有圓的並能夠覆蓋這個多邊形。
對於多邊形內部的某個點,離它最近的多邊形頂點到它的距離記爲 ,就是要求 的最大值。
對每個頂點用中垂線做半平面交求出最近範圍,那麼區域內離它最遠的多邊形內的點要麼是半平面交的頂點,要麼是多邊形與半平面交的交點。
這個半平面交就是 圖,點數和邊數都是 級別的,所以可以枚舉邊與多邊形的每條邊相交,總複雜度
Code:
#include<bits/stdc++.h>
#define maxn 2015
using namespace std;
const double eps = 1e-10;
int n;
int sgn(double x){return x>eps?1:x<-eps?-1:0;}
struct Pt{
double x,y;
Pt(double x=0,double y=0):x(x),y(y){}
Pt operator + (const Pt &p)const{return Pt(x+p.x,y+p.y);}
Pt operator - (const Pt &p)const{return Pt(x-p.x,y-p.y);}
Pt operator * (const double &t)const{return Pt(x*t,y*t);}
double operator * (const Pt &p)const{return x*p.y-y*p.x;}
double len(){return sqrt(x*x+y*y);}
}a[maxn],p[maxn];
struct Line{
Pt p,v; double ang;
Line(Pt p=0,Pt v=0,bool flg=1):p(p),v(v){if(flg) ang=atan2(v.y,v.x);}
bool operator < (const Line &L)const{return sgn(ang-L.ang)?ang<L.ang:v*(L.p-p)<0;}
}L[maxn],q[maxn];
int cnt,l,r;
bool Onleft(Pt p,Line L){return L.v*(p-L.p)>0;}
Pt Inter(Line a,Line b){return a.p+a.v*((b.v*(a.p-b.p))/(a.v*b.v));}
void HalfPlane(){
sort(L+1,L+1+cnt),q[l=r=1]=L[1];
for(int i=2;i<=cnt;i++) if(sgn(L[i].ang-L[i-1].ang)){
while(l<r&&!Onleft(p[r-1],L[i])) r--;
while(l<r&&!Onleft(p[l],L[i])) l++;
q[++r]=L[i],p[r-1]=Inter(q[r-1],q[r]);
}
while(l<r-1&&!Onleft(p[r-1],q[l])) r--;
p[r]=Inter(q[r],q[l]);
}
bool InPolygon(Pt p){
bool crs=0;
for(int i=1;i<=n;i++){
if(!sgn((a[i]-p).len()+(a[i+1]-p).len()-(a[i+1]-a[i]).len())) return 1;
double u=a[i].y,v=a[i+1].y;
if(u==v||p.y<min(u,v)||p.y>=max(u,v)) continue;
crs^=(a[i].x+(a[i+1].x-a[i].x)*(p.y-u)/(v-u))>p.x;
}
return crs;
}
bool Intersect(Line a,Line b){
return ((b.p-a.p)*a.v) * ((b.p+b.v-a.p)*a.v) < 0
&& ((a.p-b.p)*b.v) * ((a.p+a.v-b.p)*b.v) < 0;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lf%lf",&a[i].x,&a[i].y); a[n+1]=a[1];
double ans=0;
for(int i=1;i<=n;i++){
L[cnt=1]=Line(Pt(-1e4,-1e4),Pt(1,0));
L[++cnt]=Line(Pt(1e4,-1e4),Pt(0,1));
L[++cnt]=Line(Pt(1e4,1e4),Pt(-1,0));
L[++cnt]=Line(Pt(-1e4,1e4),Pt(0,-1));
for(int j=1;j<=n;j++) if(i^j){
Pt o=a[i]+a[j],v=a[j]-a[i];
L[++cnt]=Line(Pt(o.x/2,o.y/2),Pt(-v.y,v.x));
}
HalfPlane(),p[r+1]=p[l];
for(int j=l;j<=r;j++){
if(InPolygon(p[j])) ans=max(ans,(p[j]-a[i]).len());
for(int k=1;k<=n;k++){
Line x=Line(p[j],p[j+1]-p[j],0),y=Line(a[k],a[k+1]-a[k],0);
if(Intersect(x,y)) ans=max(ans,(Inter(x,y)-a[i]).len());
}
}
}
printf("%.9f\n",ans);
}