Problem Set(CF)

Problem Set(CF)

收录CF好题

显然模拟是不行的 根据数据范围应该是直接计算

点的范围是极大的,估计只能走到一小部分点

哪怕纵坐标一直不变,极限情况下也就是 \(\log _a^t\)

\(\log _{2}^{10^{16}}\) 大约 \(50\)

图片来自题解 https://www.luogu.com.cn/blog/syksykCCC/solution-cf1292b

https://www.luogu.com.cn/blog/syksykCCC/solution-cf1292b

贪心从左向右选择 (当然自己在点上肯定要的)

假设先选离自己最近的点 Q

极限情况下 \(a_x=2\,b_x=0\,a_y=1\,b_y=0\)

\(dis(Q,Q+1)=(a_x \times x_i + b_x)-a_x=a_x = dis (Q,0)=a_x\)

极端情况下才相等

所以只要向右跑一定不如向左跑

直接枚举每个向左跑的点和第一个向右跑的点(可能没有)

因为不知道从哪个点开始向左最优,也可能直接向右就拿一个最优

134数据有点绝

\(code\)

#include<bits/stdc++.h>
#define int long long
#define pt putchar
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x])
using namespace std;

const int N=66;
int ax,ay,bx,by,xs,ys,t,n,ans;
int x[N],y[N];

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}
int dis(int a,int b,int c,int d) {return llabs(a-b)+llabs(c-d);}

void init()
{
	for(int i=2;;i++)
	{
		x[i]=ax*x[i-1]+bx;
		y[i]=ay*y[i-1]+by;
		if(x[i]>xs && y[i]>ys && dis(xs,x[i],ys,y[i])>t)
		{
			n=i;
			break;
		}
	}
}

signed main()
{
	x[1]=fr(),y[1]=fr(),ax=fr(),ay=fr(),bx=fr(),by=fr();
	xs=fr(),ys=fr(),t=fr();
	
	init();
	
	for(int i=1;i<=n;i++)
	{
		int val=dis(xs,x[i],ys,y[i]),cnt=1; //自己
		int nowx=x[i],nowy=y[i];
		if(val>t) continue;
		
		if(i>1) //turn left
		{
			for(int j=i-1;j;j--)
			{
				val+=dis(nowx,x[j],nowy,y[j]);
				if(val>t) break;
				cnt++;
				nowx=x[j],nowy=y[j];
			}
		}
		
		if(val<t) //turn right
		{
			for(int j=i+1;j<=n;j++)
			{
				val+=dis(nowx,x[j],nowy,y[j]);
				if(val>t) break;
				cnt++;
				nowx=x[j],nowy=y[j];
			}
		}
		ans=max(ans,cnt);
	}
	fw(ans);

	return 0;
}


T2 Ehab the Xorcist

构造题

这里用的技巧也是先假设答案序列

性质1 : \(sum(xor) \leq sum(add)\)

\(val(xor_{a_i}) \leq val(a_i)\)

很好理解 原来 \(sum\) 与当前数二进制某一位重复了,则该位贡献没有

\(xor\) 就是不完全加法

性质2 \(sum(xor)\)\(sum(add\)) 奇偶性相同

序列 \(x\) 个奇数 \(x\) 为奇 \(sum(xor)\) 也为奇 \(sum(add)\) 同理

为偶同理

所以无解为上述情况

如何构造?

\(u == v\) 直接特判即可

显然的 \(sum(xor)\) 不太好构造

那么干脆直接放个 \(u\) 上去 剩下两两配对 \(xor\) 完直接为0

反正 \(u \lt v\)

这样就保证了 \(sum(xor)\) 接下来保证 \(sum(add)\)

剩下的 \(xor\) 起来为0,为了使序列最短,就整两个就行了

那么这样 就构造成功了 \(n=3\) 的情况

\(w[i]=[a=u,b=(v-u)/2,c=(v-u).2]\)

其实应该从 \(\frac{(v-u)}{2}\) 如果 \(v\;mod\;2\,!=\,u\;mod\;2\) 构造不合理来推断无解情况的

特判下 组合的情况 因为这个时候 \(u,v\) 都保证了可以尝试缩成2

#include<bits/stdc++.h>
#define int long long
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x])
using namespace std;

const int N=5e6+10;
int u,v;

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

signed main()
{
	u=fr(),v=fr();
	if(!u && !v) puts("0");
	else if(u>v || (u&1)!=(v&1)) puts("-1");
	else if(u==v) puts("1"),fw(u);
	else
	{
		int a=u,b=(v-u)/2,c=(v-u)/2;
		if((a^(b+c))==u)
		{
			puts("2");
			fw(a),pt,fw(b+c);
		}
		else if(((a+b)^c)==u)
		{
			puts("2");
			fw(a+b),pt,fw(c);
		}
		else
		{
			puts("3");
			fw(a),pt,fw(b),pt,fw(c);
		}
	}

	return 0;
}

T3 Absolute Sorting

题目给了一个 \(x\) 的大致范围,暴力的话直接枚举肯定超时

想办法把这个区间缩小一下,直到枚举不会超时

考虑最终的序列,只用保证相邻两项单调不减即可 即 \(|a_i-x| \leq |a_{i+1}-x|\)

  1. \(a_i \lt a_{i+1}\)

    \(0 \leq x \leq \lfloor \frac{a_i+a_{i+1}}{2} \rfloor\)

  2. \(a_i \gt a_{i+1}\)

    \(\lceil \frac{a_i+a_{i+1}}{2} \rceil \leq x\)

  3. \(a_i == a_{i+1}\)

    \(0 \to + \infty\)

不断缩小边界 \(l\) \(r\) 求得一解

注意上取整不要用ceil 用+1的形式

max(c,ceil((a+b)/2) -> max(c,(a+b+1)/2)

复杂度 \(O(n)\)

\(code\)

#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=2e5+10;
int t,n;
int a[N];

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

int solve(int l,int r)
{
	for(int i=1;i<n;i++)
	{
		if(a[i]==a[i+1]) continue;
		if(a[i]<a[i+1]) r=min(r,(a[i]+a[i+1])/2);
		else l=max(l,(a[i]+a[i+1]+1)/2);
	}
	return (l<=r)?l:-1;
}

signed main()
{
	t=fr();
	while(t--)
	{
		n=fr();
		for(int i=1;i<=n;i++) a[i]=fr();
		fw(solve(0,1e9)),puts("");
	}

	return 0;
}

T4 Permutation Game

一眼 贪心 or 数学

注意:两个人不会将所有的数染色,只会染那些位置错误的数。

肯定不能模拟,所以需要计算一个什么来直接判断

定义

\(p1:a_i ==i\,\,and\,\,a_i\not= n-i+1\)

\(p2:a_i==n-i+1\,\,and\,\,a_i\not=i\)

\(p3:a_i\not=i\,\,and\,\,a_i \not= n-i+1\)

这里注意 \(p1\,\,and\,\,p2\) 需要同时满足两项,才需要染色

刚开始只能染色,先手显然先染色 \(p2\) 染色 \(p1\) 是傻了 染色 \(p3\) 给后手坐享其成

同样后手先染色 \(p1\)

染完 \(p1\,p2\) 接着染色 \(p3\)

如果中途哪个抽风排列,另一个人可以直接换回来

等价于没变

如果 \(p2+p3 \leq p1\) 先手赢

如果 \(p1+p3 \lt p2\) 后手赢

剩下的两个人可以靠反复拉扯变成平局

#include<bits/stdc++.h>
using namespace std;

const int N=5e5+10;
int n,t,p1,p2,p3,x;

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

int main()
{
	t=fr();
	while(t--)
	{
		n=fr();
		p1=p2=p3=0;
		for(int i=1;i<=n;i++)
		{
			x=fr();
			p1+=(x==i && x!=n-i+1);
			p2+=(x==n-i+1 && x!=i);
			p3+=(x!=i && x!=n-i+1);
		}
		if(p2+p3<=p1) puts("First");
		else if(p1+p3<p2) puts("Second");
		else puts("Tie");
	}

	return 0;
}

T5 Yet Another Tournament

题面非常难懂

建议看下题解的题目大意 link

对于这种没有思路的题,先假设答案,观察性质,反推思路即可

考虑自己第 \(k\)

原来的 \(i\) 选手 最多排名 \(-1\) ,当且仅当 \(i+1\) 选手输了

排到第 \(k\)

  1. \(n-k+1\) 场 即使不打 \(n-k\) 也能排到第 \(k\)

  2. \(n-k\) 场 但是必须打赢 \(n-k+1\)

eg:要第一名 赢 \(n\) 场 或者 赢 \(n-1\) 场 但是必须赢 \(n\)

至于剩下的人 排名高的你大不到,排名低的威胁不到你

发现具有二分性 考虑二分答案

另外, 把 \(a_i\) 从小到大排序 贪心选择

2的情况细分

  1. 如果原最小组合包含了 \(n-k+1\) 成立
  2. 贪心选择原最小组合中最大的 \(a_i\)\(a_{n-k+1}\) 交换 看强选是否成功

\(code\)

#include<bits/stdc++.h>
#define int long long
#define pi pair<int,int>
#define val first
#define id second
using namespace std;

const int N=5e5+10;
int n,m,t;
pi w[N];
int to[N],s[N];

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

bool check(int mid)
{
	if(s[n-mid+1]<=m) return 1; //情况1
	if(s[n-mid]<=m && to[n-mid+1]<=n-mid) return 1; //情况2.1 最小的组合包含n-mid
	if(s[n-mid]<=m && (mid==n?0:s[n-mid-1])<=m-w[to[n-mid+1]].val) return 1; //情况2.2 强选
	return 0;
}

signed main()
{
	t=fr();
	while(t--)
	{
		n=fr(),m=fr();
		for(int i=1;i<=n;i++) 
			w[i]={fr(),i};
		
		sort(w+1,w+1+n);
		for(int i=1;i<=n;i++)
		{
			s[i]=s[i-1]+w[i].val;
			to[w[i].id]=i;
		}
		
		int l=1,r=n+1,ans=n+1;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(check(mid)) r=mid-1,ans=mid;
			else l=mid+1;
		}
		fw(ans),puts("");
	}

	return 0;
}

T6 Different Arrays

多段转移 \(\to\) dp

口胡容斥一下 不重复就是 \(2^{n-2}\)

口胡性质 :在进行第 \(i\) 次操作时,当\(a_{i+1} \not= 0\) 时产生两次贡献,为0时加减等价产生一次贡献。

因为每次操作转移的时候只会影响 \(a_{i+2}\)\(a_i\)

对后面的影响仅在 \(a_{i+2}\)


划分集合:

每一种操作序列 按照操作数为 \(i-2\) 时,第 \(i+1\) 位的值划分集合。

\(f[i][j]\) 指针为 \(i-1\),序列 \(a_{i+1}\) 位为 \(j\)

此时 \(a_{i+1}\) 相对 \(i-1\)\(i+2\)

每次递归必然为搜索树中的一种,复杂度为 \(O(nT)\)

\(T\) 为搜索的值域

大概为 \(-300 \times 300 \to 300 \times 300\)

注意值域可能为负数,加上数组偏移量 \(S\)

\(code\)

#include<bits/stdc++.h>
using namespace std;

const int N=310,Q=998244353,S=90005;
int n;
int w[N],f[N][2*N*N];

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

int dfs(int now,int val) //当前指针为 now-1 当前 a(now)==val a(now-1+2)+-a(now)
{
	if(now==n) return 1; //进行了 n-2 次操作
	if(~f[now][val+S]) return f[now][val+S]; //记忆化
	return f[now][val+S]=(dfs(now+1,w[now+1]+val)%Q+(val!=0)*dfs(now+1,w[now+1]-val)%Q)%Q;
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) w[i]=fr();
	memset(f,-1,sizeof f);
	cout<<dfs(2,w[2]); //未进行一次操作 当前i=1 a(i+1)=a2

	return 0;
}

T7 Doremy's Experimental Tree

容斥+最小生成树

源自大佬思路 https://zhuanlan.zhihu.com/p/587885558

题意

  • 给定一棵树带权树

  • 定义一种操作为任取两个树上的点 用长度为1的边连接

  • 给出所有点到环上距离自己最小的点的距离之和

  • 假设两点为\(i\;j\) 则距离和 \(d\) 记为 \(f(i,j)\)

  • 给定所有 \(f(i,j)\) 还原出整棵树


性质1

\(f(x,x)\)入手 显然是剩下树上点到自己的最短路。

如何求两点间的距离 考虑每一条边的贡献。

任取树上两个点 \(u\;v\)

图中数字为每条边被在最短路上的次数。

下为大佬图片 已授权。

性质2

所有环之外的点 到达 \(u\;v\) 的边提供的贡献相同。

比如最右上角的那条边 两个x的情况 都只被算了一遍。

性质3

\(d(u,u)+d(v,v)\) 和中 环内的每条边被计算 \(n\) 次。

上图两个 \(x\) 之间 \(6+3=2+7=9=n\)

形象理解 记第一个图中 \(x\)\(u\) 第二个中 \(x\)\(v\)\(u\;v\) 中间的点为 \(c\)

\(u\) 上面 \(v\) 下面 数量相同。

这里算的时 \(d(u,u)+d(v,v)\) 每条边总和被遍历次数 下同。

\(u \to c\) 第一个图中 这条边被 \(c\) 下面(含 \(c\) ) 每个点加一次。

\(c \to u\) 第二个图中 这条边被 \(u\) 上面(含 \(u\) ) 每个点加一次。

\(c\) 下面的点,\(u\) 上面的点 \(+\,u + c =n\)

得证。

环之外的点产生的边权贡献全部减掉。

\(edge(u,v)\) = \(\frac{f(u,u)+f(v,v)-2 \times f(u,v)}{n}\)


跑最小生成树 去掉无用边。

证明 一条无用边 \(u \to v\)\(u \to i \to ... \to j \to v\)

\(u\;v\) 之间至少有一个点。

那么按照 kru 算法 排序后 \(u \to i\)\(i \to ...\;j \to v\) 的边一定先被选 。

轮到 \(u \to v\) 这条边时已经联通。

得证。


流程

1 预处理任意两点距离

for(int i=1;i<=n;i++)
  for(int j=1;j<=n;j++)
   if(i!=j) ed[++m]={i,j,(f[i][i]+f[j][j]-2*f[i][j])/n};
   

2 kru 最小生成树


#include<bits/stdc++.h>
#define ll long long
using namespace std;

const int N=2010;
int n,m;
int p[N];
ll f[N][N];
struct node{
	int u,v;
	ll w;
	bool operator<(const node&Q)const
	{
		return w<Q.w;
	}
}ed[N*N];

ll fr()
{
	ll x=0;
	char ch=getchar();
	while(ch<'0' || ch>'9') ch=getchar();
	while(ch>='0' && ch<='9')
	{
		x=x*10+(ch-'0');
		ch=getchar();
	}
	return x;
}

int find(int x)
{
	if(x!=p[x]) p[x]=find(p[x]);
	return p[x];
}

void kru()
{
	for(int i=1;i<=n;i++) p[i]=i;
	sort(ed+1,ed+1+m);
	int cnt=0;
	for(int i=1;i<=m;i++)
	{
		int a=ed[i].u,b=ed[i].v;
		ll c=ed[i].w;
		int pa=find(a),pb=find(b);
		if(pa!=pb)
		{
			p[pa]=pb;
			cnt++;
			printf("%d %d %lld\n",a,b,c);
		}
		if(cnt==n-1) break;
	}
}

int main()
{
	cin>>n;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=i;j++)
			f[i][j]=f[j][i]=fr();
	
	for(int i=1;i<=n;i++)
	  for(int j=1;j<=n;j++)
	    ed[++m]={i,j,(f[i][i]+f[j][j]-2*f[i][j])/n};
	
	kru();

	return 0;
}


T8 All Possible Digits

左侧为最高位,右侧为最低位 \(2 \leq p \leq 1e9\)

\(p=5 \; \;a=[2,3,4]\;\;x=2 \times 5^2 +3 \times 5^1+4 \times 5^0\)

虽然 \(p\) 很大,但是操作只有 \(+1\) 一种!

考虑最低位贡献 \(a[1] \to p-1\)

剩下的贡献就是 \(a[i]\) 本身

一旦 \(a[0]\) 进位,可以产生 \(0 \to p-1\) 的贡献

找到最右侧最左侧空缺点,然后分类讨论下就行了

注意 \(a[1]\) 会变,要先记下来

\(code\)

#include<bits/stdc++.h>
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x])
using namespace std;

const int N=110;
int n,t,p;
int a[N];
unordered_set<int> s;

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

void solve()
{
	s.clear();
	memset(a,0,sizeof a);
	n=fr(),p=fr();
	for(int i=n;i;i--)
		a[i]=fr(),s.insert(a[i]);
	if(s.size()==p) {puts("0");return;}
	
	int l=0,r=p-1;
	while(s.count(l)) l++;
	while(s.count(r)) r--;
	
	if(a[1]<l) {fw(r-a[1]),nl;return;} //不进位
	int t=a[1];
	for(int i=1;i<=n;i++) //进位
	{
		a[i]=0,a[i+1]++;
		if(a[i+1]!=p) break;
	}
	for(int i=1;i<=n;i++) s.insert(a[i]);
	if(a[n+1]) s.insert(a[n+1]); //特判下全进位情况
	
	a[1]=t,l=a[1]-1;
	while(s.count(l)) l--;
	fw(p-a[1]+max(l,0)),nl;
}

signed main()
{
	t=fr();
	while(t--) solve();

	return 0;
}


T9 Restore the Permutation

首先根据样例推测,偶数位放 \(b[i]\)

然后考虑基数位

为了保证字典序最小,可以贪心的从后往前放,这样无后效性,方便了很多

这样构造有一个显然的无解,就是 \(b\) 数组有重复的数

\(b\) 数组的数放完之后,剩下的数存到一个 \(set\) 里面去

每次二分找恰好 \(\leq\) 自己的数,给前面留更多空间,避免了前面选了小数字,后面放不了的情况

当然这里也有一个无解情况,因为后面是时刻最优,当匹配不了的时刻就是无解

\(code\)

#include<bits/stdc++.h>
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define val first
#define id second
#define pb push_back
#define go(it) for(auto &it:as[x]) //注意加了&
using namespace std;

const int N=2e5+10;
int n,t;
int p[N],b[N];
unordered_set<int> s;
set<int> rest;

int fr(){ //double 不能快读!!!!
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

void solve()
{
	n=fr();s.clear();rest.clear();
	for(int i=1;i<=(n>>1);i++) b[i]=fr();
	for(int i=1;i<=n;i++) p[i]=0;
	
	//首先保证max 偶数位放原数
	for(int i=2;i<=n;i+=2)
	{
		if(!s.count(b[i/2]))
		{
			p[i]=b[i/2];
			s.insert(p[i]);
		}
		else {puts("-1");return;} //无解1
	}
	
	//基数位贪心放
	for(int i=1;i<=n;i++)
		if(!s.count(i)) rest.insert(i);
	for(int i=n-1;i>=1;i-=2)
	{
		auto it=rest.lower_bound(p[i+1]);
		if(it==rest.begin()) {puts("-1");return;} //无解2
		it--;
		p[i]=*it;
		rest.erase(it);	
	}
	
	for(int i=1;i<=n;i++) fw(p[i]),pt;
	nl;
}

int main()
{
	t=fr();
	while(t--) solve();

	return 0;
}

T10 Air Conditionern

维护温度值域,每次求交集即可,类似之前缩范围的一个题

\(init:L=R=m\)

\(L-=t_{i+1}-t_i\;R+=t_{i+1}-t_i\)

\(R=min(R,r_i)\;L=max(L,l_i)\)

这样保证了每次都满足要求

T11 a-Good String

首先 \(2^k\) 考虑分治

要想直接变这个字符串有点困难

为什么不枚举所有情况,然后直接和原串对比,看看需要该多少呢

发现一个很好的性质 每次递归减半 \(O(nlogn)\)

直接爆搜,然后决策把那一边变成 \(aaaa\) 这种形式,再加个最优性剪枝就行了

void dfs(int l,int r,char now,int cnt)
{
	if(cnt>=ans) return;
	if(l==r) {ans=min(ans,cnt+(s[l]!=now));return;}
	int cnt1=0,cnt2=0,mid=(l+r)>>1;
	for(int i=l;i<=mid;i++) cnt1+=(s[i]!=now);
	for(int i=mid+1;i<=r;i++) cnt2+=(s[i]!=now);
	dfs(l,mid,now+1,cnt2+cnt),dfs(mid+1,r,now+1,cnt1+cnt); 
}

T12 Edge Weight Assignment

所有叶子节点到根路径的异或值相同

先设置一个度不为 \(1\) 的点为根,方便考虑

首先明显两个问题

最少值

根据样例,不是 \(1\),就是 \(3\)

最大值 \(dp\) 口胡容斥下

首先,极致贪心,每条边都不一样,然后考虑哪些情况必须边权相等.

到了倒数第二层的节点,所有儿子都是叶子节点,就一条边,必须相等

因为边权是任意的,所以可以构造一种方案使剩下的边都不相同,每个叶子节点到根节点异或和为 \(0\)

\(ans=\Sigma edge - \Sigma (cnt_x-1)\;\;[\,\forall\;son_x=leaf]\)

#include<bits/stdc++.h>
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x])
using namespace std;

const int N=1e5+10;
int n,u,v,g;
int f[N];
vector<int> as[N];
vector<int> leaf;

int fr(){
    int x=0,flag=1;
    char ch=getchar();
    while(ch<'0' || ch>'9'){
        if(ch=='-') flag=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9'){
        x=x*10+(ch-'0');
        ch=getchar();
    }
    return x*flag;
}
void fw(int x){
	if(x<0) putchar('-'),x=-x;
    if(x>9) fw(x/10);
    putchar(x%10+'0');
}
int max(int a,int b){return a>b?a:b;}
int min(int a,int b){return a<b?a:b;}

void dfs(int x,int rt,int dep)
{
	if(as[x].size()==1)
	{
		f[rt]++;
		leaf.pb(dep),g=dep&1;
		return;
	}
	go(it) if(it!=rt) dfs(it,x,dep+1); 
}

int main()
{
	n=fr();
	for(int i=1;i<n;i++)
	{
		u=fr(),v=fr();
		as[u].pb(v);
		as[v].pb(u);
	}
	
	for(int i=1;i<=n;i++)
		if(as[i].size()!=1) {dfs(i,-1,0);break;}
	int res=1,ans=n-1;
	for(auto it:leaf) if(g!=(it&1)) {res=3;break;}
	fw(res),pt;
	for(int i=1;i<=n;i++) if(f[i]) ans-=f[i]-1;
	fw(ans);
	return 0;
}


T13 Sad powers

首先 \(p \geq 3\) 的直接算就可以了

然后 \(p==2\) 本来是 \(sqrtl(n)\) 但是 \(p \geq 3\) 的中的还有平方数,会重复

那么就把 \(x=a^p (p \geq 3)\;and\;x=b^2\) 的数容斥去掉就可以了

\(l,r\) 就前缀和一下就行了

#include<bits/stdc++.h>
#define int long long
#define pt putchar(' ')
#define nl puts("")
#define pi pair<int,int>
#define pb push_back
#define go(it) for(auto &it:as[x]) //注意加了&
using namespace std;

const int N=1e18;
int n,q,cnt;
vector<int> as;

int fr() //double不能快读
{
	int x=0,flag=1;
	char ch=getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch=='-') flag=-1;
		ch=getchar();
	}
	while(ch>='0' && ch<='9')
	{
		x=x*10+(ch-'0');
		ch=getchar();
	}
	return x*flag;
}

void fw(int x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) fw(x/10);
	putchar(x%10+'0');
}

int calc(int x)
{
	return sqrtl(x)+(upper_bound(as.begin(),as.begin()+cnt,x)-as.begin()); 
    //upper找的就是这个数下标从0开始 0 1 2 3 4 5 比如下标是5 前面就是有5个数
}

signed main()
{	
	for(int i=2;i*i*i<=N;i++)
	{
		int x=i*i;
		while(x<=N/i)
		{
			x*=i;
			int t=sqrtl(x);
			if(abs(t*t-x)>1e-8) as.pb(x); //平方数去掉
		}
	}
	
	sort(as.begin(),as.end());
	cnt=unique(as.begin(),as.end())-as.begin(); //排序去重下
	
	q=fr();
	for(int i=1;i<=q;i++)
	{
		int l=fr(),r=fr();
		fw(calc(r)-calc(l-1)),nl;
	}
	
	return 0;
}

T14 Binary Cards

\(n\ leq 40\) 我们考虑如何省去一半然后搜索解决

首先每个 \(2^k\) 最多选一次,否则不如 \(2^{k+1}\)

\(2^k\)\(-2^k\) 等价于 \(-2^k\)\(2^{k+1}\),没必要同时选 \(2^k\)\(-2^k\)

按二进制位考虑,有 \(1\) 必选 \(2^k\) 或者 \(-2^k\) 搜索即可,相当于用个 \(1\) 消去这些

没有 \(1\) 必不选,直接到下一层去,这样一定可以保证所有数全部消干净

每层都去重,因为二进制位的减少,每层的不同类数都在减少

\(O(n\log w)\)

const int N=1e5+10,M=19;
int n;
vector<int> num;

vector<int> dfs(int dep)
{
	if(dep>20) return vector<int>(0); //返回一个 size=0 的vector
	sort(num.begin(),num.end());
	auto it=unique(num.begin(),num.end()); //返回第一个重复元素的下标
	num.erase(it,num.end()); //前闭后开
	
	bool fl=0;
	for(auto v:num) fl|=v&1;
	if(!fl)
	{
		for(auto &v:num) v>>=1; //注意要用 & !!! 否则 num 里面还是没变
		return dfs(dep+1);
	}
	
	vector<int> base=num;
	num.clear();
	for(auto v:base)
	{
		if(v&1) num.pb((v-1)>>1); //选正的凑
		else num.pb(v>>1); //没有 1 不用凑
	}
	vector<int> ans1=dfs(dep+1);
	ans1.pb(1<<dep);
	
	num.clear();
	for(auto v:base)
	{
		if(v&1) num.pb((v+1)>>1); //选负的相当于接下来需要消去 2^{k+1} 就进到上一位
		else num.pb(v>>1);
	}
	vector<int> ans2=dfs(dep+1);
	ans2.pb(-(1<<dep));
	return (ans1.size()<ans2.size())?ans1:ans2;
}

int main()
{
	n=fr();
	for(int i=1;i<=n;i++) num.pb(fr());
	vector<int> ans=dfs(0);
	fw(ans.size()),nl;
	for(auto v:ans) fw(v),pt;

	return 0;
}

T15 Rest In The Shades

考虑任意 \(AB\) 上一点和所求点的连线,设改点为 \(C\),连起来就是三角形 \(ABC\)

image

设连线交点 \(A'\;B'\) 我们预处理 \(A'\) \(B'\) 中间这些,如图就是 \([5,6]\)(前缀和预处理即可),然后二分 \(A'\) 所在线段处理这个不整段

再根据相似三角形找出来这些 \(x\) 轴上的投影

const int N=2e5+10;
int n,xa,xb,sy,q;
double s[N],segl[N],segr[N];
set<pair<double,int>> dot;

double calc(int l,int r){return (r>=l)?(s[r]-s[l-1]):0.0;}

signed main()
{
	sy=fr(),xa=fr(),xb=fr(),n=fr();
	for(int i=1;i<=n;i++)
	{
		scanf("%lf%lf",&segl[i],&segr[i]);
		s[i]=s[i-1]+segr[i]-segl[i];
		dot.insert({1.0*segl[i],i}),dot.insert({1.0*segr[i],i});
	}
	q=fr();
	while(q--)
	{
		int x=fr(),y=fr();
		double len1=1.0*y/(y-sy)*fabs(x-xa);
		double l=(x<xa)?(x+len1):(x-len1);
		double len2=1.0*y/(y-sy)*fabs(x-xb);
		double r=(x<xb)?(x+len2):(x-len2);
		
		auto it1=dot.lower_bound({l,0});
		auto it2=dot.lower_bound({r,0});
		if(it1==dot.end())
		{
			puts("0.0000000000"); //注意输出
			continue;
		}
		int L=it1->second;
		if(it2==dot.end()) it2--,r=segr[n];
		int R=it2->second;
		double val=0;
		if(L==R)
		{
			if(r>segl[L] && l>=segl[L]) val=r-l;
			else if(l<segl[L] && r>=segl[L]) val=r-segl[L];
			else val=0;
		}
		else val=segr[L]-max(l,segl[L])+max(0.0,r-segl[R]);
		double ans=calc(L+1,R-1)+val;
		ans*=1.0*(y-sy)/y;
		printf("%.10lf\n",ans);
	}

	return 0;
}

T16 Rain of Fire

首先二分答案 \(x\)

考虑直接 \(O(n^2)\) 建边,然后 \(dsu\) 判联通性

分类讨论一下,新加一个点,如果在那种十字路口的话,可以最多连接 \(4\) 个连通块,设连通块个数为 \(cnt\),新点为 \(now\)

\(cnt==1\) \(ok\)

\(cnt==2\)

image

image

\(cnt==3\)

image

\(cnt==4\)

image

考虑新增点,只要该位置可以和某个连通块联通,等价于拥有了这个连通块的"颜色",一个点拥有的颜色多少就可以判断能联通几个连通块

因为这个新点的位置坐标肯定是由两个原来的点控制的,我们再次 \(O(n^2)\) 枚举出新点即可

\(cnt \leq 2\) 直接枚举即可

\(3\leq cnt \leq 4\)

\(g_{i,j}\) 表示 \(i\) 号连通块和 \(j\) 号连通块联通,然后给它一个编号,到时候 \(1<<id\) 就代表

考虑到不同的 \(x\)\(y\) 分别都只有 \(n\) 种,我们直接离散化,这样就可以开下一个 \(n \times n\) 的数组,把每个点作为新点能拥有几种"颜色"记下来

const int N=1e3+10;
int n;
int x[N],y[N],p[N],col[N],link[N][N];
struct node{int x,y;}dot[N];
const int g[5][5]={ //状压数组
	{0,0,0,0,0},
    {0,0,1,2,3},
    {0,1,0,4,5},
    {0,2,4,0,6},
    {0,3,5,6,0}
};

int find(int x)
{
	if(x!=p[x]) p[x]=find(p[x]);
	return p[x];	
}
int count(int x)
{
	int res=0;
	while(x) x-=x&(-x),res++;
	return res;
}

bool check(int T)
{
	for(int i=1;i<=n;i++) p[i]=i,col[i]=0; //连通块编号
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			if(find(i)!=find(j))
			{
				if(dot[i].x==dot[j].x && abs(y[dot[i].y]-y[dot[j].y])<=T) p[find(i)]=find(j);
				else if(dot[i].y==dot[j].y && abs(x[dot[i].x]-x[dot[j].x])<=T) p[find(i)]=find(j);
			}
	int cnt=0;
	for(int i=1;i<=n;i++)
		if(!col[find(i)]) col[find(i)]=++cnt;
	if(cnt==1) return 1;
	else if(cnt==2)
	{
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++)
				if(find(i)!=find(j))
				{
					if(dot[i].x==dot[j].x && abs(y[dot[i].y]-y[dot[j].y])<=(T<<1)) return 1;
					else if(dot[i].y==dot[j].y && abs(x[dot[i].x]-x[dot[j].x])<=(T<<1)) return 1;
					else if(abs(x[dot[i].x]-x[dot[j].x])<=T && abs(y[dot[i].y]-y[dot[j].y])<=T) return 1;
				}
	}
	else if(cnt==3 || cnt==4)
	{
		memset(link,0,sizeof link);
		for(int i=1;i<=n;i++)
			for(int j=i+1;j<=n;j++)
				if(find(i)!=find(j) && abs(x[dot[i].x]-x[dot[j].x])<=T && abs(y[dot[i].y]-y[dot[j].y])<=T)
				{
					link[dot[i].x][dot[j].y]|=1<<g[col[find(i)]][col[find(j)]];
					link[dot[j].x][dot[i].y]|=1<<g[col[find(i)]][col[find(j)]];
				}
		if(cnt==3)
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					if(count(link[i][j])>=2) return 1;
		}
		else
		{
			for(int i=1;i<=n;i++)
				for(int j=1;j<=n;j++)
					if(count(link[i][j])>=4) return 1;
		}
	}
	return 0;
}

signed main()
{
	n=fr();
	for(int i=1;i<=n;i++) x[i]=dot[i].x=fr(),y[i]=dot[i].y=fr();
	sort(x+1,x+1+n),sort(y+1,y+1+n);
	for(int i=1;i<=n;i++)
	{
		dot[i].x=lower_bound(x+1,x+1+n,dot[i].x)-x;
		dot[i].y=lower_bound(y+1,y+1+n,dot[i].y)-y;
	}
	
	int l=1,r=1e18,ans=INT_MAX;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) r=mid-1,ans=mid;
		else l=mid+1;
	}
	if(ans!=INT_MAX) fw(ans);
	else puts("-1");
	
	return 0;
}
posted @ 2023-04-09 12:13  xyzfrozen  阅读(18)  评论(0)    收藏  举报