难题训练(二)

2020 Petrozavodsk Winter Camp, Jagiellonian U Contest.Problem G. Invited Speakers

给出两个大小为 n n n的点集,其中所有点的横纵坐标都不一样,给两个点集做匹配,两个点之间的匹配是坐标系中若干条平行于坐标轴的首尾相连的线段,求使得不同匹配不相交的匹配方案。

因为横纵坐标都不一样,按照横坐标排序两个点集,然后对位匹配,确定一个大常数 C C C,走 a i → ( a i . x , C + i ) → ( C + i , C + i ) → ( C + i , − C − i ) → ( b i . x , − C − i ) → b i a_i \rightarrow (a_i.x,C+i) \rightarrow (C+i,C+i) \rightarrow (C+i,-C-i) \rightarrow (b_i.x,-C-i)\rightarrow b_i ai(ai.x,C+i)(C+i,C+i)(C+i,Ci)(bi.x,Ci)bi即可。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 10
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n;
pair<int,int>A[maxn],B[maxn];

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%d",&n);
		int C = 0;
		rep(i,1,n) scanf("%d%d",&A[i].first,&A[i].second) , C = max(C , max(abs(A[i].first) , abs(A[i].second)));
		rep(i,1,n) scanf("%d%d",&B[i].first,&B[i].second) , C = max(C , max(abs(B[i].first) , abs(B[i].second)));
		sort(A+1,A+1+n,greater<pair<int,int> >()),sort(B+1,B+1+n,greater<pair<int,int> >());
		rep(i,1,n){
			vector<pair<int,int> >G;
			G.push_back(A[i]);
			G.push_back(make_pair(A[i].first,C+i));
			G.push_back(make_pair(C+i,C+i));
			G.push_back(make_pair(C+i,-C-i));
			G.push_back(make_pair(B[i].first,-C-i));
			G.push_back(B[i]);
			G.resize(unique(G.begin(),G.end())-G.begin());
			printf("%d\n",G.size());
			rep(j,0,G.size()-1) printf("%d %d\n",G[j].first,G[j].second);
		}
	}
}

Problem I. Sum of Palindromes

给出一个 1 e 5 1e5 1e5位数 x x x,求不超过 log ⁡ 1 e 5 \log 1e5 log1e5个回文数之和为 x x x

假设 x x x的位数为 n n n,则取前 ⌈ n 2 ⌉ \lceil\frac n2\rceil 2n位数,然后将其减一,再强行给后 ⌊ n 2 ⌋ \lfloor\frac n2\rfloor 2n调配一个数使得这个数是回文数,那么每次数的大小会 ÷ 2 \div2 ÷2
注意可能会有减一之后就不是 n n n位数的情况,简单判断即可。
A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 100005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n;
char a[maxn],b[maxn];

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%s",a);
		n = strlen(a);
		vector<string>ans;
		reverse(a,a+n);
		for(;n;){
			
			if(n == 2 && a[1] == '1' && a[0] == '0'){
				ans.push_back("1");
				ans.push_back("9");
				break;
			}
			if(n == 1){
				ans.push_back(string(1,a[0]));
				break;
			}
			
			int x = n/2+1;
			rep(i,0,n-1) b[i] = 0;
			int tp = 0;
			per(i,n-1,x) b[i] = a[i];
			b[x-1] = a[x-1] - 1;
			rep(i,x-1,n-2) if(b[i] < '0') b[i] += 10 , b[i+1]--;
			string s;
			if(b[n-1] == '0'){
				per(i,n-2,x-1) b[n-2-i] = b[i];
				if(!b[x-2]) b[x-2] = '9';
				s = string(b,b+n-1);
			}
			else{
				per(i,n-1,x-1) b[n-1-i] = b[i];
				s = string(b,b+n);
			}
			ans.push_back(s);
			rep(i,0,n-1) a[i] = a[i] - (b[i] - '0');
			rep(i,0,n-2) if(a[i] < '0') a[i] += 10 , a[i+1] --;
			for(;a[n-1] == '0';n--);
		}
		
		printf("%d\n",ans.size());
		rep(i,0,ans.size()-1) printf("%s\n",ans[i].c_str());
	}
}

Problem H. Lighthouses

给出一个凸包和一些凸包上点之间的连边,求一个在边上移动的方案使得不重复经过任何点(包括线段相交的点)的最长长度。

因为是凸包,所以每次选择了一个点之后就把可选点集分成了两部分,走任意一部分都会导致另一部分不可选了,直接 O ( n 3 ) O(n^3) O(n3)区间 d p dp dp即可。
A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 305
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define db double
using namespace std;
int T,n,m;
struct Point{
    db x,y;
}p[maxn];
db sqr(db x){return x*x;}
db dis(Point A,Point B){ return sqrt(sqr(A.x-B.x)+sqr(A.y-B.y));}
int a[maxn][maxn];
db f[maxn][maxn],g[maxn][maxn];
int main()
{
    scanf("%d",&T);
    while(T--){
        scanf("%d",&n);
        memset(a,0,sizeof a);
        db ans=0;
        rep(i,0,n-1) scanf("%lf%lf",&p[i].x,&p[i].y);
        scanf("%d",&m);
        for(int i=1;i<=m;++i){
            int u,v;
            scanf("%d%d",&u,&v);
            u--;v--;a[u][v]=a[v][u]=1;
        }
		rep(i,0,n-1) rep(j,0,n-1) f[i][j]=g[i][j]=-1e18;
        rep(i,0,n-1) f[i][i]=g[i][i]=0;
		rep(d,0,n-1) rep(l,0,n-1){
                int r=(l+d)%n;
                for(int k=(r+1)%n;k!=l;k=(k+1)%n){
                    if(a[l][k])
                        g[l][k]=max(g[l][k],f[l][r]+dis(p[l],p[k])),
                        f[k][r]=max(f[k][r],f[l][r]+dis(p[l],p[k]));
                    if(a[r][k])
                        g[l][k]=max(g[l][k],g[l][r]+dis(p[r],p[k])),
                        f[k][r]=max(f[k][r],g[l][r]+dis(p[r],p[k]));
                }
                ans=max(ans,max(f[l][r],g[l][r]));
        	}
        printf("%.10lf\n",ans);
    }
}

A. Bags of Candies

一组可以包含两个不互质的数或一个数,求将 1 1 1 n n n分组最少分几组。

最多配对数的上界:求出 n ≥ p > n 2 n\geq p\gt \frac n2 np>2n的所有质数 p p p的数量 D ( n ) D(n) D(n),那么 D ( n ) D(n) D(n)中的所有数还有 1 1 1这个数都是不可能配对的,最多配对数的上界即为 ⌊ n − D ( n ) − 1 2 ⌋ \lfloor\frac {n-D(n)-1}2\rfloor 2nD(n)1

可以构造出这个上界:
把所有不在 D ( n ) D(n) D(n)中的不是 1 1 1的数按照最大质因子分组。
那么对于一组,这些数两两之间都可以配对,如果个数为偶数就直接全部配对了,如果个数为奇数,因为他们的最大质因子 x ≤ n 2 x\leq \frac n2 x2n,所以 2 x 2x 2x一定在这组中,把除了 2 x 2x 2x以外的数全部配对,最后每组都可能会剩下一个 2 2 2的倍数,继续两两配对即可达到上界。

所以求区间质数即可。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 1000006
#define LL long long
using namespace std;

int sn,cnt;
LL a[maxn],s[maxn],n;
inline int ID(LL a){ return a <= sn ? a : cnt - (n/a) + 1; }
double inv[maxn];

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%lld",&n);
		cnt = 0;
		sn = sqrt(n);
		for(LL i=1;i<=n;i++) 
			a[++cnt] = (i = n / (n / i)) , s[cnt] = i-1;
		int cntp = 0;
		for(int i=2;i<=sn;i++) if(s[i]^s[i-1]){
			inv[i] = 1.0 / i;
			for(LL j=cnt,LIM=1ll*i*i;a[j]>=LIM;j--)
				s[j] = (s[j] - (s[ID((LL)(a[j] * inv[i] + 1e-9))] - cntp));
			cntp ++;
		}
		int t = s[cnt] - s[ID(n/2)] + 1;
		printf("%lld\n",(n-t+1)/2 + t);
	}
}

Problem D.Clique

给出圆上一些弧,两个弧之间有公共部分则视为有连边,求最大团。
枚举一个弧满足最大团中没有被它完全覆盖的弧,
最大团中的其他弧就一定包含左右至少一个端点,
包含两个端点的一定和其他的弧都相邻。
对于包含一个端点的,
我们把它按照包含的那个端点分成两半,两边长度分别为为 ( x , y ) (x,y) (x,y)
那么对于包含端点 A A A的互相相邻,包含端点 B B B的互相相邻,
需要在两边选出若干个弧使得任意两个弧有公共部分。
则任选左边一个弧 ( x a , y a ) (x_a,y_a) (xa,ya),和右边一个弧 ( x b , y b ) (x_b,y_b) (xb,yb)
我们需要的就是 x a + x b ≥ x_a+x_b \geq xa+xb我们最开始枚举的弧的长度 L e n a Lena Lena , 或者 y a + y b ≥ y_a + y_b \geq ya+yb周长减去我们最开始枚举的弧的长度 L e n b Lenb Lenb
( x b , y b ) (x_b,y_b) (xb,yb)变成 ( L e n a − x b , L e n b − y b ) (Lena - x_b , Lenb - y_b) (Lenaxb,Lenbyb),就变成了
A A A中的选出的点不能两维都 < \lt < B B B中选出的点。
按照横坐标排序后从大到小线段树维护 d p dp dp即可求出最多能选出的弧。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lc u<<1
#define rc lc|1
#define Ct const
#define Le 1000000
using namespace std;

int n,sl[maxn],tr[maxn];
int mx[maxn<<2],ad[maxn<<2],sb[maxn<<2];
int cnta ,cntb;
pair<int,int>A[maxn<<2],B[maxn<<2];

int dis(int a,int b){ return (b-a+Le) % Le; }

int isi(int x,int l,int r){ 
	if(r < l) return x >= l || x <= r;
	return l <= x && x <= r;
}

void Build(int u,int l,int r){
	ad[u] = mx[u] = 0;
	if(l == r) return;
	int m = l+r>>1;
	Build(lc,l,m),Build(rc,m+1,r);
}

void upd(int u){
	mx[u] = max(mx[lc] , mx[rc]);
}

void dtp(int u,int v){
	ad[u] += v, mx[u] += v;
}

void dt(int u){
	if(ad[u]){
		dtp(lc,ad[u]),dtp(rc,ad[u]);
		ad[u] = 0;
	}
}

void ins(int u,int l,int r,int p,int v){
	if(l==r) return(void)(mx[u] = v);
	int m=l+r>>1;dt(u);
	p <=m ? ins(lc,l,m,p,v) : ins(rc,m+1,r,p,v);
	upd(u);
}

void add(int u,int l,int r,int ql,int qr,int v){
	if(ql>r||l>qr||ql>qr) return;
	if(ql<=l&&r<=qr) return (void)(dtp(u,v));
	int m=l+r>>1;dt(u);
	add(lc,l,m,ql,qr,v),add(rc,m+1,r,ql,qr,v);
	upd(u);
}

int qry(int u,int l,int r,int ql,int qr){
	if(ql>r||l>qr||ql>qr) return 0;
	if(ql<=l&&r<=qr) return mx[u];
	int m=l+r>>1;dt(u);
	return max(qry(lc,l,m,ql,qr) , qry(rc,m+1,r,ql,qr));
}
 
int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%d",&n);
		rep(i,1,n)
			scanf("%d%d",&sl[i],&tr[i]);
		int ans = 0;
		rep(i,1,n){
			int sm = 0;
			sb[0] = cnta = cntb = 0;
			int Lena = dis(sl[i] , tr[i]) , Lenb = Le - Lena;
			rep(j,1,n) if(isi(sl[i],sl[j],tr[j]) && isi(tr[i],sl[j],tr[j])) sm++;
				else if(isi(tr[i],sl[j],tr[j])){
					sb[++sb[0]] = dis(tr[i],tr[j]);
					A[++cnta] = make_pair(dis(sl[j],tr[i]) , dis(tr[i],tr[j]));
				}
				else if(isi(sl[i],sl[j],tr[j])){
					sb[++sb[0]] = Lenb - dis(sl[j],sl[i]);
					B[++cntb] = make_pair(Lena - dis(sl[i],tr[j]) , Lenb - dis(sl[j],sl[i]));
				}
			if(!sb[0]){
				ans = max(ans , sm);
				continue;
			}
			sort(sb+1,sb+1+sb[0]);
			sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
			rep(i,1,cnta) A[i].second = lower_bound(sb+1,sb+1+sb[0],A[i].second) - sb;
			rep(i,1,cntb) B[i].second = lower_bound(sb+1,sb+1+sb[0],B[i].second) - sb;
			Build(1,1,sb[0]);
			sort(A+1,A+cnta+1,greater<pair<int,int> >());
			sort(B+1,B+cntb+1,greater<pair<int,int> >());
			for(int i=1,j=1;i<=cnta || j<=cntb;){
				if(j > cntb || (i <= cnta && A[i].first >= B[j].first)){
					add(1,1,sb[0],1,A[i].second,1);
					i++;
				}
				else{
					int t = qry(1,1,sb[0],1,B[j].second);
					ins(1,1,sb[0],B[j].second,t);
					add(1,1,sb[0],B[j].second,sb[0],1);
					j++;
				}
			}
			ans = max(ans , sm + qry(1,1,sb[0],1,sb[0]));
		}
		printf("%d\n",ans);
	}
}

E - Contamination

给出平面上 n n n个不相交的圆,每次给出 ( p x , p y ) , ( q x , q y ) , y m i n , y m a x (p_x,p_y),(q_x,q_y),y_{min},y_{max} (px,py),(qx,qy),ymin,ymax,求在纵坐标在 y m i n y_{min} ymin y m a x y_{max} ymax之间(不包含端点)能否不经过圆从 p p p点走到 q q q点。

因为圆不相交,所以无法到达的唯一情况就是在 [ min ⁡ ( p x , q x ) , max ⁡ ( p x , q x ) ] [\min(p_x,q_x),\max(p_x,q_x)] [min(px,qx),max(px,qx)]中有一个圆的圆心在里面,而且这个圆的上边界和下边界分别超过了 y m i n y_{min} ymin y m a x y_{max} ymax(其实相切也走不过去)。

按照 y y y写扫描线遇到圆的上边界就插入圆的下边界,在 y m i n y_{min} ymin处查询区间最值和 y m a x y_{max} ymax的大小关系即可。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 1000005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define Ct const
using namespace std;

int n,q;
int ans[maxn],tot;
int px[maxn],qx[maxn],py[maxn],qy[maxn],ym[maxn],yx[maxn];

struct opt{
	int y,x,dy,t;
	bool operator <(const opt &B)const{
		return y == B.y ? t < B.t : y < B.y;
	}
}Q[maxn*2];

#define inf 0x3f3f3f3f
#define maxp maxn * 30
int mx[maxp],rt,cnt,lc[maxp],rc[maxp];
void ins(int &u,int l,int r,int p,int v){
	if(!u) u = ++cnt , mx[u] = -inf;
	mx[u] = max(mx[u] , v);
	if(l == r) return;
	int m = l+r>>1;
	p <= m ? ins(lc[u] , l, m ,p,v) : ins(rc[u],m+1,r,p,v);
}
int qry(int u,int l,int r,int ql,int qr){
	if(ql>r||l>qr||ql>qr||!u) return -inf;
	if(ql<=l&&r<=qr) return mx[u];
	int m = l+r>>1;
	return max(qry(lc[u],l,m,ql,qr),qry(rc[u],m+1,r,ql,qr));
}

int main(){
	scanf("%d%d",&n,&q);
	rep(i,1,n){
		int x,y,r;
		scanf("%d%d%d",&x,&y,&r);
		Q[++tot].y = y - r , Q[tot].x = x , Q[tot].t = -1 , Q[tot].dy = y + r;
	}
	rep(i,1,q){
		scanf("%d%d%d%d%d%d",&px[i],&py[i],&qx[i],&qy[i],&ym[i],&yx[i]);
		Q[++tot].y = ym[i] , Q[tot].t = i , Q[tot].dy = yx[i];
	}
	
	sort(Q+1,Q+1+tot);
	rep(i,1,tot){
		if(Q[i].t <= 0)
			ins(rt,-inf,inf,Q[i].x,Q[i].dy);
		else{
			int l = px[Q[i].t] , r = qx[Q[i].t];
 			ans[Q[i].t] = (qry(rt,-inf,inf,min(l,r),max(l,r)) < Q[i].dy);
		}
	}
	rep(i,1,q) printf("%s\n",ans[i] ? "YES" : "NO");
}

F. The Halfwitters

n n n个士兵排成一列,编号为 1... n 1...n 1...n,有三种操作,花费 a a a交换相邻士兵,花费 b b b把排列前后翻转,花费 c c c将排列变为随机排列,多次给出排列求将排列变为 1... n 1...n 1...n的最小期望花费。

假设变为随机排列的期望次数为 X + C X+C X+C,那么在不用 c c c的情况下我们可以很简单的求出逆序对为 k k k的一个排列的花费 f k = min ⁡ ( a k , b + n ( n − 1 ) 2 a ) f_k=\min(ak,b+\frac {n(n-1)}2a) fk=min(ak,b+2n(n1)a),而实际上的花费为 g k = min ⁡ ( f k , X + c ) g_k = \min(f_k , X + c) gk=min(fk,X+c)
又因为 X n ! = ∑ p 是 一 个 排 列 g p 的 逆 序 对 数 Xn! = \sum_{p是一个排列} g_{p的逆序对数} Xn!=pgp,所以我们把 f k f_k fk排个序为 { h i } \{h_i\} {hi},枚举一个 j j j使得 h j ≤ X + c h_j \leq X + c hjX+c, h h + 1 > X + c h_{h+1} \gt X+c hh+1>X+c,那么就可以得到方程 X n ! = ∑ i = 0 j h j + ( n ( n − 1 ) 2 − j ) ( X + c ) Xn! = \sum_{i=0}^j h_j + (\frac {n(n-1)}2-j)(X+c) Xn!=i=0jhj+(2n(n1)j)(X+c),解出来满足条件即可。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 300
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long 
using namespace std;

int n,a,b,c,d,id[maxn],ar[17];
LL dp[maxn],cnt[17][maxn],fac[17];

bool cmp(const int &u,const int &v){ return dp[u] < dp[v]; }

LL gcd(LL a,LL b){ return !b ? a : gcd(b,a%b); }

int main(){
	int T;
	cnt[0][0] = fac[0] = fac[1] = 1;
	rep(i,1,16) rep(j,0,i*(i-1)/2) rep(k,0,min(i-1,j))
		cnt[i][j] = cnt[i][j] + cnt[i-1][j-k];
	rep(i,2,16) fac[i] = fac[i-1] * i;
	for(scanf("%d",&T);T--;){
		scanf("%d%d%d%d%d",&n,&a,&b,&c,&d);
		int N = n * (n-1) / 2;
		rep(i,0,N) dp[i] = min(a * i , b + a * (N - i)) , id[i] = i;
		sort(id,id+1+N,cmp); 
		LL S = 0 , Sz = fac[n] , fm , fz;
		long double x = 1e20;
		rep(i,0,N-1){
			int u = id[i];
			S += dp[u] * cnt[n][u] , Sz -= cnt[n][u];
			
			fm = fac[n] - Sz , fz = S + Sz * c;
			x = (long double)fz / fm;
			if(x + c >= dp[u] && x + c <= dp[id[i+1]]){
				fz += c * fm;
				LL d = gcd(fz , fm);
				fz /= d , fm /= d;
				break;
			}
		}
		for(;d--;){
			rep(i,1,n) scanf("%d",&ar[i]);
			int cnt = 0;
			rep(i,1,n) rep(j,i+1,n) if(ar[j] < ar[i])	cnt ++;
			if(dp[cnt] <= x + c) printf("%lld/1\n",dp[cnt]);
			else printf("%lld/%lld\n",fz,fm);
		}
	}
}

J. Space Gophers

给出 1 e 6 3 1e6^3 1e63的立方体空间直角坐标系。
给出若干条 ( x , y , − 1 ) (x,y,-1) (x,y,1)代表 ( x , y , t ) , t ∈ [ 1 , 1 e 6 ] (x,y,t),t\in[1,1e6] (x,y,t),t[1,1e6]是空的(还有 ( − 1 , y , z ) (-1,y,z) (1,y,z) ( x , − 1 , z ) (x,-1,z) (x,1,z)),其他的都是非空的。
给出两个格子问能否通过空的六联通格子互相到达。

把所有的 ( x , y , − 1 ) (x,y,-1) (x,y,1)等看做一个点,那么两个点直接连通有两种:
这两个点 − 1 -1 1的位置相同,那么 ( x , y ) (x,y) (x,y)需要四联通。
这两个点 − 1 -1 1的位置不同,那么都不是 − 1 -1 1的那个位置需要差 ≤ 1 \leq 1 1

第一种用 m a p < p a i r < i n t , i n t > , i n t > map,int> map<pair<int,int>,int>暴力做。
第二种用 v e c t o r < i n t > a [ x ] [ y ] [ z ] vectora[x][y][z] vector<int>a[x][y][z],存下不是 − 1 -1 1的位置是 x x x − 1 -1 1的位置是 y y y x x x上的坐标是 z z z的所有点的标号,注意到第二种连边是会把 v e c t o r vector vector中所有点连完的,所以我们把 v e c t o r vector vector中所有点和当前点连完边之后只留一个点不会影响答案,同时还可以保证复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)的。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 300005
#define pb push_back
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int dir[5][2]={{1,0},{0,1},{-1,0},{0,-1},{0,0}};
vector<int>ONE[3][2][1000006];
map<pair<int,int>,int>TWO[3];
int n,F[maxn];
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }

void link(int x,int y){
	if(!y) return;
	x = Find(x) , y = Find(y);
	if(x ^ y) F[x] = y;
}

int get(int a,int b,int c){
	if(TWO[0].count(make_pair(b,c)))
		return Find(TWO[0][make_pair(b,c)]);
	if(TWO[1].count(make_pair(a,c)))
		return Find(TWO[1][make_pair(a,c)]);
	if(TWO[2].count(make_pair(a,b)))
		return Find(TWO[2][make_pair(a,b)]);
	return -1;
}

void links(int x,vector<int>&y){
	if(!y.empty()){
		for(;y.size() > 1;y.pop_back())
			link(x,y.back());
		link(x,y.back());
	}
}

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		rep(i,0,2){
			TWO[i].clear();
			rep(j,0,1) rep(k,0,1000000)
				ONE[i][j][k].clear();
		}
		scanf("%d",&n);
		rep(i,1,n) F[i] = 0;
		rep(i,1,n){
			int x,y,z;
			scanf("%d%d%d",&x,&y,&z);
			if(x == -1){
				links(i,ONE[1][1][y]);
				links(i,ONE[2][1][z]);
				links(i,ONE[1][1][y-1]);
				links(i,ONE[2][1][z-1]);
				links(i,ONE[1][1][y+1]);
				links(i,ONE[2][1][z+1]);
				rep(j,0,4){
					int u = y + dir[j][0] , v = z + dir[j][1];
					if(TWO[0].count(make_pair(u,v)))
						link(i,TWO[0][make_pair(u,v)]);
				}
				ONE[1][0][y].pb(i);
				ONE[2][0][z].pb(i);
				TWO[0][make_pair(y,z)] = i;
			}
			if(y == -1){
				links(i,ONE[0][1][x]);
				links(i,ONE[2][0][z]);
				links(i,ONE[0][1][x-1]);
				links(i,ONE[2][0][z-1]);
				links(i,ONE[0][1][x+1]);
				links(i,ONE[2][0][z+1]);
				rep(j,0,4){
					int u = x + dir[j][0] , v = z + dir[j][1];
					if(TWO[1].count(make_pair(u,v)))
						link(i,TWO[1][make_pair(u,v)]);
				}
				ONE[0][0][x].pb(i);
				ONE[2][1][z].pb(i);
				TWO[1][make_pair(x,z)] = i;
			}
			if(z == -1){
				links(i,ONE[0][0][x]);
				links(i,ONE[1][0][y]);
				links(i,ONE[0][0][x-1]);
				links(i,ONE[1][0][y-1]);
				links(i,ONE[0][0][x+1]);
				links(i,ONE[1][0][y+1]);
				rep(j,0,4){
					int u = x + dir[j][0] , v = y + dir[j][1];
					if(TWO[2].count(make_pair(u,v)))
						link(i,TWO[2][make_pair(u,v)]);
				}
				ONE[0][1][x].pb(i);
				ONE[1][1][y].pb(i);
				TWO[2][make_pair(x,y)] = i;
			}
		}
		int q;
		scanf("%d",&q);
		for(;q--;){
			int a,b,c,x,y,z;
			scanf("%d%d%d%d%d%d",&a,&b,&c,&x,&y,&z);
			int A = get(a,b,c)  ,B = get(x,y,z);
			if(A == B) puts("YES");
			else puts("NO");
		}
	}
}

K.To argue,or not to arguethis is a question

给出 n × m n\times m n×m的网格,其中有一些点不能用,现在要放入 2 k 2k 2k个人,每个人一个点,其中 2 i − 1 2i-1 2i1 2 i 2i 2i是一对,问没有一对人在相邻的格子的方案数。

容斥变为问有至少 p p p对人在相邻的格子的方案数。
直接插头 d p dp dp求放 p p p 1 × 2 1\times2 1×2的一对人的方案数,然后剩下的方案数可以直接组合数算一算。
时间复杂度 O ( 2 12 k n m ) O(2^{12}knm) O(212knm)
A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 150
#define mod 1000000007
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m,K;
char s[maxn][maxn];
int C[maxn][maxn],f[2][1<<12][maxn],fac[maxn];

void add(int &a,int b){ (a += b) >= mod && (a -= mod); }

int main(){
	int T;
	rep(i,C[0][0]=1,maxn-1) rep(j,C[i][0]=1,i)
		C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
	fac[0] = fac[1] = 1;
	rep(i,2,maxn-1) fac[i] = fac[i-1] * 1ll * i % mod;
	for(scanf("%d",&T);T--;){
		scanf("%d%d%d",&n,&m,&K);
		rep(i,0,n-1)
			scanf("%s",s[i]);
		int cnt = 0;
		rep(i,0,n-1) rep(j,0,m-1) if(s[i][j] == '.')
			cnt ++;
		if(n < m){
			swap(n,m);
			rep(i,0,max(n,m)-1) rep(j,i+1,max(n,m)-1) swap(s[i][j] , s[j][i]);
		}
		
		int now = 1 , pre = 0;
		memset(f,0,sizeof f);
		f[now][0][0] = 1;
		int M = (1 << m) - 1;
		rep(i,0,n-1) rep(j,0,m-1){
			swap(now,pre);
			rep(p,0,(1<<m)-1) rep(k,0,K) if(f[pre][p][k]){
				if(s[i][j] == '.'){
					add(f[now][p | (1 << j)][k] , f[pre][p][k]);
					if(p >> j & 1) add(f[now][p & (M - (1<<j))][k+1],f[pre][p][k]);
					if(j) if(p >> (j-1) & 1)
						add(f[now][p & (M - (1<<j) - (1<<j-1))][k+1],f[pre][p][k]);
				}
				else
					add(f[now][p & (M - (1<<j))][k],f[pre][p][k]);
				f[pre][p][k] = 0;
			}
		}
		int ans = 0;
		rep(k,0,K){
			int r = 0;
			rep(p,0,(1<<m)-1) add(r , f[now][p][k]);
			r = 1ll * r * C[K][k] % mod * fac[k] % mod;
			rep(j,k,K-1) r = 1ll * r * C[cnt-2*j][2] % mod;
			if(k&1) r = 1ll * (mod - 1) * r % mod;
			ans = (ans + r) % mod;
		}
		rep(i,1,K) ans = 1ll * ans * 2 % mod;
		printf("%d\n",ans);
	}
}

2018 Petrozavodsk Winter Camp, Yandex Cup Problem I. ≤  or  ≥ 

给出 n n n个大小为 10 10 10的栈,每次只告诉你栈顶,你可以给出一个数 x x x,然后交互库给出 ≤ \leq ≥ \geq 中的一个符号 R R R并且让所有满足栈顶 R x Rx Rx成立的栈弹栈,求 50 50 50次操作内把所有栈清空。

假设目前第 i i i个栈的大小为 s z i sz_i szi
那么我们设一个权值为 ∑ 3 s z i \sum 3^{sz_i} 3szi
将栈顶元素按照大小排序,找出中间那个小于等于它和大于等于它的栈的 3 s z i 3^{sz_i} 3szi都分别不小于总的 3 s z i 3^{sz_i} 3szi的栈,然后将其栈顶输出,那么无论交互库给出什么符号都能使得 ∑ 3 s z i \sum 3^{sz_i} 3szi中的一半除上 3 3 3,所以一次操作可以使 ∑ 3 s z i \sum 3^{sz_i} 3szi变成 ≤ 2 3 ∑ 3 s z i \leq \frac 23 \sum_{3^{sz_i}} 323szi,所以可以 50 50 50次操作内清零。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 10005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,K,a[maxn],c[maxn],sz[maxn],pw[11],sm;
bool cmp(const int &u,const int &v){ return a[u] < a[v]; }

int main(){
	scanf("%d%d",&n,&K);
	pw[1] = 1;
	rep(i,2,10) pw[i] = 3 * pw[i-1]; 
	rep(i,1,n) c[i]=i,sz[i] = K;
	rep(i,1,n) scanf("%d",&a[i]);	
	for(;;){
		sort(c+1,c+1+n,cmp);
		sm = 0;
		rep(i,1,n) sm += pw[sz[i]];
		int tmp = 0;
		rep(i,1,n){
			tmp += pw[sz[c[i]]];
			if(tmp * 2 >= sm){
				printf("%d\n",tmp = a[c[i]]);
				fflush(stdout);
				break;
			}
		}
		char ch[5];
		scanf("%s",ch);
		if(ch[0] == 'E') break;
		if(ch[0] == '<'){
			rep(i,1,n) 
				if(sz[i] && a[i] <= tmp)
					sz[i] --;
		}
		else{
			rep(i,1,n)
				if(sz[i] && a[i] >= tmp)
					sz[i] --;
		}
		rep(i,1,n) scanf("%d",&a[i]);
	}
}

Problem J.Stairways

将有序的 n n n个人分到两个队列,在队列中保持原来的顺序,如果队列中前后两个人 x , y x,y x,y满足 t x > t y t_x > t_y tx>ty,则 t y t_y ty变为 t x t_x tx,并造成 t x − t y t_x - t_y txty的代价,求最小的代价。
n ≤ 1 e 5 n \leq 1e5 n1e5

如果只有一个序列,那么设前 i i i个中的最大值为 p r e i pre_i prei,则代价为 ∑ i p r e i − t i \sum_i pre_i - t_i ipreiti
现在我们要从这个序列中剥离出一个新的序列,那么我们可以从后往前 d p dp dp
d p i , j dp_{i,j} dpi,j表示考虑完后 i i i个数,新序列的目前的第一个数的值 ≥ j \geq j j的最小代价。
那么第 i i i个数可以放入新序列,并且第 i i i个数可以适当增大来适应序列前面的数,
所以有 j > t i j > t_i j>ti时, d p i , j = d p i + 1 , j + j − t i dp_{i,j}=dp_{i+1,j}+j-t_i dpi,j=dpi+1,j+jti
j < t i j < t_i j<ti时,因为我们的定义是第 i i i个数 ≥ j \geq j j,那么最小代价可以是第 i i i个数不变直接放入新序列也就是 d p i + 1 , t i dp_{i+1,t_i} dpi+1,ti,也可以是放入旧序列,代价为 d p i + 1 , j + p r e i − t i dp_{i+1,j}+pre_i-t_i dpi+1,j+preiti,这两个取 min ⁡ \min min
用线段树维护 d p dp dp,我们需要区间加等差数列,单点查询,区间加之后对一个值 c h k m i n chkmin chkmin
发现 d p i , j dp_{i,j} dpi,j根据我们的定义是单调不下降的,所以区间加之后对一个值 x x x c h k m i n chkmin chkmin一定可以表示成区间的一段前缀区间加,区间后半部分赋值为 x x x,二分出分界点之后即可直接写区间加,区间赋值解决。

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 100005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define lc u<<1
#define rc lc|1	
#define LL long long
using namespace std;

int n,sb[maxn],h[maxn],pre[maxn];
LL ad[maxn<<3],kt[maxn<<3],fz[maxn<<3],val[maxn<<3];
void dtpf(int u,LL v){
	val[u] = v , ad[u] = 0 , kt[u] = 0 , fz[u] = v;
}
void dtf(int u){
	if(fz[u] != -1){
		dtpf(lc,fz[u]),dtpf(rc,fz[u]);
		fz[u] = -1;
	}
}
void dtp(int u,LL v,LL k){
	dtf(u);
	val[u] += v , ad[u] += v , kt[u] += k;
}
void dt(int u,int l,int m,int r){
	if(ad[u] || kt[u]){
		dtp(lc,ad[u],kt[u]),dtp(rc,ad[u]+kt[u]*(sb[m+1]-sb[l]),kt[u]);
		ad[u] = kt[u] = 0;
	}
	dtf(u);
}
void add(int u,int l,int r,int ql,int qr,LL v,LL k){
	if(ql>r||l>qr||ql>qr) return;
	if(ql<=l&&r<=qr) return (void)(dtp(u,v,k));
	int m = l+r>>1;dt(u,l,m,r);
	add(lc,l,m,ql,qr,v,k),add(rc,m+1,r,ql,qr,v+k*max(0,sb[m+1]-sb[max(l,ql)]),k);
}
void ins(int u,int l,int r,int ql,int qr,LL v){
	if(ql>r||l>qr||ql>qr) return;
	if(ql<=l&&r<=qr) return (void)(dtpf(u,v));
	int m =l+r>>1;dt(u,l,m,r);
	ins(lc,l,m,ql,qr,v)  ,ins(rc,m+1,r,ql,qr,v);
}
LL qry(int u,int l,int r,int p){
	if(l==r) return val[u];
	int m=l+r>>1;dt(u,l,m,r);
	return p <= m ? qry(lc,l,m,p) : qry(rc,m+1,r,p);
}

int main(){
	scanf("%d",&n);
	memset(fz,-1,sizeof fz);
	rep(i,1,n) scanf("%d",&h[i]),sb[++sb[0]]=h[i];
	sort(sb+1,sb+1+sb[0]);sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
	rep(i,1,n) h[i] = lower_bound(sb+1,sb+1+sb[0],h[i])-sb , pre[i] = max(pre[i-1] , h[i]);
	per(i,n,1){
		add(1,1,sb[0],h[i]+1,sb[0],sb[h[i]+1] - sb[h[i]] , 1);
		int L = 1 , R = h[i] , mid;LL p = qry(1,1,sb[0],h[i]);
		for(;L<R;){
			mid = (L+R) >> 1;
			if(qry(1,1,sb[0],mid) + sb[pre[i]] - sb[h[i]] <= p) L = mid + 1; 
			else R = mid;
		}
		add(1,1,sb[0],1,L-1,sb[pre[i]]-sb[h[i]],0);
		ins(1,1,sb[0],L,h[i],p);
	}
	printf("%lld\n",qry(1,1,sb[0],1));
}

2018 Petrozavodsk Winter Camp, Yandex Cup .E.Oneness

给出近乎随机的 n ≤ 1 0 250000 n\leq 10^{250000} n10250000,求 1.. n 1..n 1..n o n e n e s s oneness oneness的和,其中一个数 k k k o n e n e s s oneness oneness被定义为 k k k的因数中除了1以外十进制表示中只包含 1 1 1的数的个数,比如 1221 = 11 ∗ 111 1221 = 11 * 111 1221=11111 o n e n e s s oneness oneness 2 2 2

就是求 ⌊ n 11 ⌋ + ⌊ n 111 ⌋ + ⌊ n 1111 ⌋ . . . . \lfloor \frac n{11} \rfloor+\lfloor \frac n{111} \rfloor+\lfloor \frac n{1111} \rfloor.... 11n+111n+1111n....
= ⌊ 9 n 99 ⌋ + ⌊ 9 n 999 ⌋ + ⌊ 9 n 9999 ⌋ . . . . =\lfloor \frac {9n}{99} \rfloor+\lfloor \frac {9n}{999} \rfloor+\lfloor \frac {9n}{9999} \rfloor.... =999n+9999n+99999n....
那么我们就得到了一个 O ( L 3 ) O(L^3) O(L3)的做法,只需要用FFT优化高精度除法即可通过此题。
十进制数对于 ÷ 9...9 \div 9...9 ÷9...9有一个巧妙的方法。
这里以 99 99 99为例子。
假设 n = 100 k + R , R ≤ 99 n = 100k + R,R \leq 99 n=100k+R,R99
那么 ⌊ n 99 ⌋ = k + ⌊ k + R 99 ⌋ \lfloor \frac n{99}\rfloor = k + \lfloor \frac {k+R}{99}\rfloor 99n=k+99k+R
从数位上看就是把百位以上的数在十进制中右移了两位,然后将这个数贡献到答案中,在将右移后的数和原来的数   m o d   100 \bmod 100 mod100的结果相加后继续这个过程,直到这个数 ≤ 99 \leq 99 99
对于 k k k 9 9 9做一次的复杂度是 O ( L 2 k ) O(\frac {L^2}k) O(kL2)的。
那么我们就得到了一个 O ( L 2 ln ⁡ L ) O(L^2\ln L) O(L2lnL)的做法。
发现如果在上面的过程中右移后的数和原来的数相加之后不会 ≥ 100 \geq 100 100也就是在后两位的最高位进位,那么对于一个数位 k k k,它右移两位会到第 k − 2 k-2 k2位,做两次会移到 k − 4 k-4 k4位,做 p p p次会到 k − 2 p k-2p k2p位。
如果我们除 a a a 9 9 9,那么第 k k k位的数 d k d_k dk会贡献到 k − a k-a ka位, k − 2 a k-2a k2a位… k − p a k-pa kpa位。
发现一个数位对他往右边 k k k位的贡献是一样的,和 k k k的约数个数有关,记做 g k g_k gk
所以我们可以直接来 d d d g g g卷积一下。
但是上面是不会假设进位的情况,实际上是会进位的,但是因为数据随机,我们又只需要知道后几位的最高位会不会进位(最高位进位之后 100 k + R 100k+R 100k+R中的 k k k才会改变,从而对答案造成贡献),可以猜测,我们只保留后几位的最高 T T T位来暴力计算是否进位是有极大概率正确的。(也就是说除了最高 T T T位后面的部分进位一直进到最高位的概率极低。)
那么对 k k k 9 9 9做一次的复杂度是 O ( L T k ) O(\frac {LT}k) O(kLT)
总复杂度就是 O ( L T ln ⁡ L ) O(LT\ln L) O(LTlnL)

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 600005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define db double
#define LL long long
#define Ct const
using namespace std;
 
Ct int T = 20; 
int L;
unsigned int s[maxn];
struct cp{
	db r,i;
	cp(Ct db &r=0,Ct db &i=0):r(r),i(i){}
	cp operator +(Ct cp &B)Ct{ return cp(r+B.r,i+B.i); }
	cp operator -(Ct cp &B)Ct{ return cp(r-B.r,i-B.i); }
	cp operator *(Ct cp &B)Ct{ return cp(r*B.r-i*B.i,r*B.i+i*B.r); }
}A[maxn],B[maxn];
int Wl,Wl2,lg[maxn];
LL ans[maxn];
cp w[maxn];
#define Pi 3.1415926535897932384626433832795
void init(int n){
	for(Wl=1;n>=Wl<<1;Wl<<=1);Wl2=Wl<<1;
	rep(i,Wl,Wl2) w[i] = cp(cos(2 * Pi * (i-Wl) / Wl2) , sin(2 * Pi * (i-Wl) / Wl2));
	per(i,Wl-1,1) w[i] = w[i<<1];
	rep(i,2,Wl2) lg[i] = lg[i>>1] + 1; 
}
 
void FFT(cp *A,int n,int tp){
	static int r[maxn];
	if(tp ^ 1) reverse(A+1,A+n);
	rep(i,0,n-1) i < (r[i] = r[i>>1] >> 1 | (i&1) << lg[n] - 1) && (swap(A[i],A[r[i]]) , 0);
	cp t;
	for(int L=1;L<n;L<<=1) for(int s=0,L2=L<<1;s<n;s+=L2) for(int k=s,x=L;k<s+L;x++,k++)
		t = A[k+L] * w[x] , A[k+L] = A[k] - t , A[k] = A[k] + t;
	if(tp ^ 1) rep(i,0,n-1) A[i].r /= n;
}
 
int main(){
	scanf("%d",&L);
	scanf("%u",&s[0]);
	rep(i,1,L-1) s[i] = 747796405u * s[i-1] - 1403630843u;
	rep(i,0,L-1) s[i] = s[i] / 1024u % 10u;
	reverse(s,s+L);
	rep(i,0,L-1) s[i] = 9 * s[i];
	rep(i,0,L-1) s[i+1] += s[i] / 10 , s[i] %= 10;
	for(;s[L];L++) s[L+1] += s[L] / 10 , s[L] %= 10;
	reverse(s,s+L);
	init(L<<1);
	rep(i,0,L-1) A[i].r = s[i];
	rep(i,2,L-1) for(int j=1,k=i;k<L;k+=i,j++)
		B[k].r ++;
	FFT(A,Wl2,1),FFT(B,Wl2,1);
	rep(i,0,Wl2-1) A[i] = A[i] * B[i];
	FFT(A,Wl2,-1);
	reverse(s,s+L);
	reverse(A,A+L);
	rep(i,0,L-1) ans[i] = round(A[i].r);
	static int a[T << 2] = {};
	rep(i,2,L){
		memset(a,0,sizeof a);
		rep(j,0,min(i-1,T)) a[T-j] = s[i-1-j];
		for(int j=i;j<=L;j+=i){
			int v = j + i - 1;
			rep(k,0,min(i-1,T)) a[T-k] += (T+i-k <= 2 * T ? a[T+i-k] : 0) + s[v-k];
			rep(k,T+1,min(T+T,T+i)) a[k] = 0;
			rep(k,0,T) a[k + 1] += a[k] / 10 , a[k] %= 10;
			ans[0] += a[T+1];
 			rep(k,T+1,min(T+T,T+i)-1) a[k+1] += a[k] / 10 , a[k] %= 10;
		}
		bool flg = 1;
		rep(k,0,min(T,i-1)) if(a[T-k] != 9) flg = 0;
		ans[0] += flg;
	}
	rep(i,0,L-1) ans[i+1] += ans[i] / 10 , ans[i] %= 10;
	for(;L>1 && ans[L-1] == 0;L--);
	per(i,L-1,0) printf("%d",ans[i]);
}

2018 Petrozavodsk Winter Camp, B. Short Random Problem

题解在里面了。

2020 Petrozavodsk Winter Camp, Jagiellonian U Contest C. Bookface

多少年前的保序回归论文题

A C   C o d e \mathcal AC \ Code AC Code

#include
#define maxn 2000005
#define LL long long
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,d,lc[maxn],rc[maxn],l[maxn],rt[maxn],sz[maxn];
LL a[maxn];
int st[maxn],h[maxn];

void merge(int &u,int l,int r){
	if(!l || !r) return (void)(u = l+r);
	if(a[l] > a[r]) u = l , merge(rc[u],rc[l],r);
	else u = r , merge(rc[u],l,rc[r]);
	if(h[lc[u]] < h[rc[u]]) swap(lc[u],rc[u]);
	h[u] = h[rc[u]] + 1;
	sz[u] = sz[lc[u]] + sz[rc[u]] + 1;
}

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%d%d",&n,&d);
		rep(i,1,n) scanf("%lld",&a[i]);
		sort(a+1,a+1+n);
		rep(i,2,n) a[i] -= (i-1ll) * d;
		LL ans = 0;
		rep(i,1,n) if(a[i] < 0)
			ans += -a[i] , a[i] = 0;
		st[0] = 0;
		rep(i,1,n){
			rt[i] = l[i] = i; sz[i] = 1 , h[i] = 1;lc[i] = rc[i] = 0;
			for(;st[0] && a[rt[st[st[0]]]] > a[rt[i]];st[0]--){
				merge(rt[i],rt[st[st[0]]],rt[i]);
				l[i] = l[st[st[0]]];
				for(;sz[rt[i]] > (i - l[i]) / 2 + 1;)
					merge(rt[i],lc[rt[i]],rc[rt[i]]);
			}
			st[++st[0]] = i;
		}
		per(i,n,1){
			for(;l[st[st[0]]] > i;st[0]--);
			ans += abs(a[i] - a[rt[st[st[0]]]]);
		}
		printf("%lld\n",ans);
	}
}

你可能感兴趣的:(难题训练(二))