文章目录
1001题
Bragging Dice
题目大意
给一个只含01的字符串s,可以对任意的奇数长度区间翻转,可操作任意次数,输出s能变成的字典序最小的字符串。
思路
奇数长度翻转,意味着翻转后不改变奇偶性
所以每个
1
1
1 可以移动到任意同奇偶的位置
贪心地把
1
1
1 移到右边即可
具体看一组样例操作吧
发现答案由三部分组成,第一部分全是
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) n(n⩽50000)和一个 1 ∼ n 1 \sim n 1∼n 的排列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]| ∣i−j∣×∣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 n−1 ,所以最小生成树的边权不会大于等于 n n n 。还记得边权的公式吗?现在是 ∣ i − j ∣ × ∣ p [ i ] − p [ j ] ∣ < n |i − j|×|p[i] − p[j]| < n ∣i−j∣×∣p[i]−p[j]∣<n,显然只需要分别枚举 ∣ i − j ∣ |i − j| ∣i−j∣ 和 ∣ 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′,1−fy′,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;
}