2022暑期杭电第八场

1001题

Bragging Dice

题目链接

题目大意

给一个只含01的字符串s,可以对任意的奇数长度区间翻转,可操作任意次数,输出s能变成的字典序最小的字符串。

思路

奇数长度翻转,意味着翻转后不改变奇偶性
所以每个 1 1 1 可以移动到任意同奇偶的位置
贪心地把 1 1 1 移到右边即可
具体看一组样例操作吧
_I86_6L@6_UHMWK____2Q0J.png
发现答案由三部分组成,第一部分全是 0 0 0,第二部分 01 01 01 交替,第三部分全是 1 1 1
我们分别统计在奇数位置和偶数位置上 1 1 1 的数量
然后按规律排列即可

代码

#include<bits/stdc++.h>
using namespace std;
string s;
void solve()
{
	int ji=0,ou=0;
	s="";
	cin>>s;
	int len=s.length();
	for(int i=0;i<len;i++)
	{
		if(s[i]=='1')	i&1 ? ou++ : ji++;
	}
	
	int minn=min(ji,ou);
	ji-=minn,ou-=minn;
	
	int tmp=len-minn*2-ji*2-ou*2;
	for(int i=1;i<=tmp;i++) printf("0");
	
	if(len&1) //奇后偶前
	{
		if(ji) for(int i=1;i<=ji;i++) printf("01");
		if(ou) for(int i=1;i<=ou;i++) printf("10");
	}
	else //奇前偶后
	{
		if(ji) for(int i=1;i<=ji;i++) printf("10");
		if(ou) for(int i=1;i<=ou;i++) printf("01");
	}
	for(int i=1;i<=minn;i++) printf("11");
	cout<<endl;
}
int main()
{
	int test;
	cin>>test; 
	while(test--)
	{
		solve();
	}
}

1007题

Darnassus

原题链接

题目大意

给你点的数量 n ( n ⩽ 50000 ) n(n \leqslant 50000) nn50000)和一个 1 ∼ n 1 \sim n 1n 的排列p。点的标号从 1 1 1 n n n,点 i i i 在排列 p p p 中对应的值为 p [ i ] p[i] p[i],任意两点间的边权为 ∣ i − j ∣ × ∣ p [ i ] − p [ j ] ∣ |i − j|×|p[i] − p[j]| ij×p[i]p[j] ,求最小生成树。

思路

n n n 的数量看起来不大,但边的数量是 n 2 n^2 n2,prim和kruskal都不好处理,但也就边的数量过多这一个问题了,所以考虑删除一些不太可能成为树边的边。

​ 观察可得点标号 i i i 和排列中对应值 p [ i ] p[i] p[i] (以下简称为值)一一对应且计算边权时完全等价,将式子中差的绝对值视为距离,则可以很容易地发现距离越近则边权往往越小。由于 i i i p [ i ] p[i] p[i] 等价,所以需要同时考虑点在标号中的距离和在排列中的距离,选择距离较近的一些边跑最小生成树算法

​ 到这一步,问题已经可解,唯一没有确定的是“距离较近的边”具体有多近。不考虑证明的话,直接在可容许的时间复杂度内选择尽量远的边即可,推荐试一试常见数,比如 l o g 2 ( n ) log2(n) log2(n) s q r t ( n ) sqrt(n) sqrt(n) 等等。由于生成树时每个点都要走一遍,所以基础复杂度就 O ( n ) O(n) O(n)了,显然也没有多少数可以选择。别整成 n 2 n^2 n2 就行

​ 正解是 s q r t ( n ) sqrt(n) sqrt(n)

​ 因为我们可以令标号差或值差尽可能地小——也就是 1 1 1,然后另外一个顶天了也就是 n − 1 n-1 n1 ,所以最小生成树的边权不会大于等于 n n n 。还记得边权的公式吗?现在是 ∣ i − j ∣ × ∣ p [ i ] − p [ j ] ∣ < n |i − j|×|p[i] − p[j]| < n ij×p[i]p[j]<n,显然只需要分别枚举 ∣ i − j ∣ |i − j| ij ∣ p [ i ] − p [ j ] ∣ ⩽ n |p[i] − p[j]| \leqslant \sqrt[]{n} p[i]p[j]n 的部分就可以完整覆盖所有可能的边。

代码

#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
using namespace std;
typedef long long ll;
inline int min(int a,int b)
{
	return a<b?a:b;
}
inline int max(int a,int b)
{
	return a>b?a:b;
}
int n,p[50005],rp[50005];
bool vis[50005];
int dis[50005];
ll prim()
{
	memset(vis,0,sizeof(vis));
	memset(dis,0x3f,sizeof(dis));
	int count=0,l,r,k=sqrt(n)+1; ll ans=0;
	priority_queue<pair<int,int> >q;
	q.push({0,(1+n)/2});
	dis[(1+n)/2]=0;
	while(1)
	{
		int t=q.top().second;
		ll val=-q.top().first;
		q.pop();
		if(vis[t])continue;
		vis[t]=1;
		ans+=val;
		count++;
		if(count==n) return ans;
		l=max(1,t-k),r=min(n,t+k);
		for(int i=l;i<=r;i++)		//枚举位置 
		{
			int dis2=abs((t-i)*(p[t]-p[i]));
			if(!vis[i] && dis2<dis[i]){
				q.push({-dis2,i});
				dis[i]=dis2;
			}
		}
		l=max(1,p[t]-k),r=min(n,p[t]+k);
		for(int i=l;i<=r;i++)		//枚举值 
		{
			int dis2=abs((t-rp[i])*(p[t]-i));
			if(!vis[rp[i]] && dis2<dis[rp[i]]){
				q.push({-dis2,rp[i]});
				dis[rp[i]]=dis2;
			}
		}
	}
}
int main()
{
	int t; scanf("%d",&t);
	while(t--)
	{
		scanf("%d",&n);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",p+i);
			rp[p[i]]=i;
		}
		printf("%lld\n",prim());
	}
	return 0;
}

1008题

Orgrimmar

原题链接

题目大意

求一棵树最大分离集的大小,换句话说,就是保留树的部分节点和他们之间的边,使得每个节点至多只有一条边与其相连,求点的集合的最大值。

思路

树形 d p dp dp ,可以发现父子节点之间有限制关系。令 f x , 0 / 1 / 2 {f_x,_{0/1/2}} fx,0/1/2 分别表示 x x x 为根的子树中,不选 x x x /选 x x x 但不选子节点连接/选 x x x 且选子节点连接,符合要求的集合最大值。

状态转移:

( y y y 为以 x x x 为根的子节点)

f x , 0 = s u m ( m a x ( f y , 0 , f y , 1 , f y , 2 ) ) {f_x,_0}=sum(max({f_y,_0},{f_y,_1},{f_y,_2})) fx,0=sum(max(fy,0,fy,1,fy,2))

f x , 1 = s u m ( f y , 0 ) + 1 {f_x,_1}=sum({f_y,_0})+1 fx,1=sum(fy,0)+1

f x , 2 = s u m ( f y , 0 ) + m a x ( f y ′ , 1 − f y ′ , 0 ) + 1 {f_x,_2}=sum({f_y,_0})+max({f_{y'},_1}-{f_{y'},_0})+1 fx,2=sum(fy,0)+max(fy,1fy,0)+1

代码

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

typedef long long ll;
typedef double db;

const int N=5e5+1;

int T,n,tot,a,b;
int head[N],ver[2*N],Next[2*N],f[N][3];

void add(int x,int y)
{
    ver[tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
    tot++;
}

void dfs(int x,int fa)
{
    int m0=0,m1=0,m2=0;//表示以x为根的三种情况
    for(int i=head[x];~i;i=Next[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        dfs(y,x);
        //状态转移
        m0+=max(max(f[y][0],f[y][1]),f[y][2]);
        m1+=f[y][0];
        m2=max(m2,f[y][1]-f[y][0]);
    }
    f[x][0]=m0;
    f[x][1]=m1+1;
    f[x][2]=m1+m2+1;
}

int main()
{
    int size(512<<20);
	__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
    
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        tot=0;
        for(register int i=1;i<=n;i++)//初始化
        {
            head[i]=-1;
            f[i][0]=0;f[i][1]=0;f[i][2]=0;
        }
        for(register int i=1;i<=n-1;i++)
        {
            scanf("%d%d",&a,&b);
            add(a,b);add(b,a);
        }
        dfs(1,0);
        printf("%d\n",max(max(f[1][0],f[1][1]),f[1][2]));
    }
    exit(0);
}

1011题

Stormwind

题目链接

题目大意

给定一个 n × m n \times m n×m大小的矩形,要求画出尽可能多的线,使得按照这些线切割后得到的小矩形面积均 ⩾ k \geqslant k k,问最多能够画多少条线

思路

如果我们确定了某一方向上这条线的间隔时,依靠面积 k k k 即可求出另外一个方向上的间隔。那么我们只需要对某一个方向上的间隔进行枚举。随后计算出另外一个方向上的间隔,分别与 n m n m nm相除即可。值得注意的是,另一方向上的间隔大小应当向上取整,保证小矩形的面积 ⩾ k \geqslant k k

代码

#include<iostream>
#include<math.h>
using namespace std;

void solve(){
    int n,m,k;
    cin>>n>>m>>k;
    int ans = 0;
    for(int i = 1;i <= k;i ++){
        double x = k*1.0 / i;
        int j = ceil(x);
        if(i > n || j > m)continue;
        ans = max(ans,n / i + m / j - 2);
    }
    cout<<ans<<endl;
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    int t;
    cin>>t;
    while(t--)solve();
    system("pause");
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值