比赛传送门
作者: fn
目录
签到题
G题 Link with Monotonic Subsequence / Link和单调子序列
题目大意
设排列的价值为其 “最长递增子序列” 和 “最长递减子序列” 的最大值。
构造一个长为
n
n
n 的价值最小的排列。
考察内容
构造,二分
分析
方法1:
二分最小权值,然后构造形如 7 8 9 10 3 4 5 6 1 2 的排列。
方法2:
直接计算出最小权值
⌈
√
n
⌉
⌈√n⌉
⌈√n⌉ ,构造同理。
方法1代码:
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll ans[N];
bool ck(ll x){
ll t1=n-x;
int p=1;
int cnt=0;
while(p<=n){
cnt++;
if(n-p+1<x){
t1+= x - (n-p+1);
}
for(int i=1;i<=x;i++){
a[p]=t1+i;
p++;
if(p>n)break;
}
t1-=x;
}
if(cnt>x)return 0;
else return 1;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int t; cin>>t;
while(t--){
cin>>n;
ll l=1,r=n;
// 二分找满足的最小的value
while(l<r){
int mid=(l+r)>>1;
if(ck(mid))r=mid; else l=mid+1;
}
ll x=l;
ll t1=n-x;
int p=1;
while(p<=n){
if(n-p+1<x){
t1+= x - (n-p+1);
}
for(int i=1;i<=x;i++){
a[p]=t1+i;
p++;
if(p>n)break;
}
t1-=x;
}
for(int i=1;i<=n;i++){
cout<<a[i]<<' ';
}
cout<<endl;
}
return 0;
}
/*
输入:
1
10
输出:
7 8 9 10 3 4 5 6 1 2
*/
基本题
J题 Link with Arithmetic Progression / Link和等差数列
题目大意
给定一个数列
a
a
a ,将其修改为一个等差数列
b
b
b,代价为
∑
i
=
1
n
(
a
i
−
b
i
)
2
∑^n _{i=1}(a_i − b_i)^2
∑i=1n(ai−bi)2 。
求最小代价。
考察内容
三分,数学知识
分析
直接求出中间位置的高度
d
d
d,然后三分斜率
k
k
k
#include<bits/stdc++.h>
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
#define double long double
using namespace std;
const int N=1e5+5;
ll n,m,a[N];
namespace GTI // 快读
{
char gc(void){
const int S = 1 << 16;
static char buf[S], *s = buf, *t = buf;
if (s == t) t = buf + fread(s = buf, 1, S, stdin);
if (s == t) return EOF;
return *s++;
}
int gti(void){
int a = 0, b = 1, c = gc();
for (; !isdigit(c); c = gc()) b ^= (c == '-');
for (; isdigit(c); c = gc()) a = a * 10 + c - '0';
return b ? a : -a;
}
}
using GTI::gti;
double d1; // 中间的数字为d1
double d2; // 中间的位置
double calc(double k){
double cost=0;
for(int i=1;i<=n;i++){
cost+=pow( k*(i-d2)+d1 -a[i] ,2);
}
return cost;
}
int main(){ // AC
int t; t=gti();
while(t--){
ll sum=0;
n=gti();
for(int i=1;i<=n;i++){
a[i]=gti();
sum+=a[i];
}
d1=sum*1.0/n; // 中间的数字为d1
d2=(n+1)*1.0/2; // 中间的位置
// 三分斜率,找calc最小时的斜率
double l=-1e9-1, r=1e9+1, mid1, mid2, p;
if(n>=100){ // 优化上下界
l=-(4e9+1)/n;
r=(4e9+1)/n;
}
while(r-l>1e-9){
p = (r-l)/3;
mid1 = l+p;
mid2 = l+p+p;
double c1=calc(mid1);
double c2=calc(mid2);
if(fabs(c1-c2) < 5e-7){ // c1,c2差别很小,直接退出
break;
}
if(c1>c2) l = mid1;
else r = mid2;
}
double ans=calc((l+r)/2);
ans=min(ans,calc(l));
ans=min(ans,calc(r)); // 更新最小值
printf("%.7Lf\n",ans);
}
return 0;
}
/*
1
5
0 100000000 200000000 300000000 400000000
1
5
-1 1 2 3 4
1
5
-1 100000000 200000000 300000000 400000000
1
4
-1000000000 -1000000000 1000000000 1000000000
1
26
1 1 4 5 1 4 1 9 1 9 8 1000000000 0 1 1 4 5 1 4 1 9 1 9 8 1 0
959999993267692577.8750000
1
3
-1 0 1
1
3
-1000000000 0 1000000000
1
3
-1000000000 1000000000 1000000000
1
2
-1000000000 1000000000
1
13
1 1 4 5 1 4 1 9 1 9 8 1 0
3
3
-1 0 1
3
0 0 1
13
1 1 4 5 1 4 1 9 1 9 8 1 0
// 按ctrl+Z结束
*/
进阶题
K题 Link with Bracket Sequence I / Link和括号序列I
题目大意
已知括号序列
a
a
a 是一个长度为
m
m
m 的合法括号序列
b
b
b 的子序列,求可能的序列
b
b
b 的数量。
(
1
≤
n
≤
m
≤
200
,
∑
m
≤
1
0
3
)
(1 \leq n \leq m \leq 200, ∑m \leq 10^3)
(1≤n≤m≤200,∑m≤103)
答案对 1 0 9 + 7 10^9+7 109+7 取模。
考察内容
三维dp
分析
记
f
i
,
j
,
k
f_{i,j,k}
fi,j,k 为序列
b
b
b 的前
i
i
i 位中,与
a
a
a 的最长公共子序列为
j
j
j ,且左括号比右括号多
k
k
k 个的方案数。
转移时分类讨论,枚举下一位填写的是哪种括号。
复杂度 O ( m 3 ) O(m^3) O(m3) , 可以通过。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=205, p=1e9+7;
int t,n,m,f[N][N][N];
char s[N];
void add(int &x,int y){ // 加法取模
x = (x+y>=p?x+y-p:x+y);
}
signed main(){ // 3维dp
scanf("%d",&t);
while(t--){
scanf("%d%d\n%s",&n,&m,s+1);
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
for(int k=0;k<=m;k++)f[i][j][k]=0;
}
}
f[0][0][0]=1; // 空串算1个
for(int i=0;i<=m;i++){ // 序列b的前i位
for(int j=0;j<=n;j++){ // 与a的最长公共子序列为j
for(int k=0;k<=i;k++){ // 左括号比右括号多k个
// 转移时枚举下一位填写的是哪种括号
if(k>=1){ // 左括号比右括号多,b的下一位可以放右括号
if(j!=n && s[j+1]==')'){
add(f[i+1][j+1][k-1], f[i][j][k]); // 对应上了,最长公共子序列+1
}
else{
add(f[i+1][j][k-1], f[i][j][k]);
}
}
// b的下一位放左括号
if(j!=n && s[j+1]=='('){
add(f[i+1][j+1][k+1], f[i][j][k]); // 对应上了,最长公共子序列+1
}
else{
add(f[i+1][j][k+1], f[i][j][k]);
}
}
}
}
printf("%d\n",f[m][n][0]); // 前m位,和a的最长公共子序列长为n,左括号比右括号多0个
}
}
D题 Link with Game Glitch / Link和游戏故障
题目大意
给定一张
n
n
n 个点,
m
m
m 条有向边的有向图,将所有边权
x
i
x_i
xi 乘以系数
w
w
w ,使得对于每一个环,环中权重的乘积
≤
1
≤1
≤1 。
(
2
≤
n
≤
1000
,
2
≤
m
≤
2000
)
(2≤n≤1000, 2≤m≤2000)
(2≤n≤1000,2≤m≤2000)
保证原先存在权重的乘积大于1的环。
考察内容
二分答案,SPFA算法
分析
由于边权乘积较大,直接去计算会爆long double的精度。需要先二分答案,然后对其取对数check。
check的问题类似于求负环,可以采用SPFA算法解决。
#include<stdio.h>
#include<math.h>
using namespace std;
const int maxn=1005,maxm=2005;
const double eps=1e-9;
int n,m;
int uu[maxm],vv[maxm],aa[maxm],cc[maxm];
double dis[maxn],ww[maxm];
int Bellman_Ford(){ // SPFA算法找环
for(int i=1;i<=n;i++)
dis[i]=0;
for(int i=1;i<n;i++){
int flg=0;
for(int j=1;j<=m;j++){
int u=uu[j],v=vv[j];
double w=ww[j];
if(dis[u]+w<dis[v])
dis[v]=dis[u]+w,flg=1;
}
if(flg==0)
return 0;
}
return 1;
}
int main(){ // 二分答案+找环
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d%d%d%d",&aa[i],&uu[i],&cc[i],&vv[i]);
// 二分答案
double L=eps,R=1-eps;
while(L+eps<R){
double mid=(L+R)/2;
for(int i=1;i<=m;i++)
ww[i]=-log(1.0*mid*cc[i]/aa[i]);
if(Bellman_Ford()==0)
L=mid;
else R=mid;
}
printf("%.6lf\n",L);
return 0;
}