构造一个n的排列,使得排列的max( lis , lds )最小
题解
可以按照sqrt(n)
来分块
Eg: n=9
排列:321 654 987
其中lis与lds都为sqrt(n),其中lds的长度来自每个块内部,lis来自所有块之间。
故:只需分块后数字倒叙输出就行
代码
#include
#include
#include
using namespace std;
void solve() {
int n;
cin>>n;
int sq=ceil(sqrt(n));//表示块数以及每个块的个数
for(int i=1;i*sq<=n;i++) {//把完整的块输出
int st=(i-1)*sq+1;//起点数值
int ed=i*sq;//终点数值
for(int j=ed;j>=st;j--) cout<(n/sq)*sq;i--) cout<>T;
while(T--) solve();
return 0;
}
题意
给定一个数组a,问将数组a变成等差数列b使得(bi-ai)*(bi-ai)之和最小
题解
线性回归定义:
故:题目转化为,一堆点(i,ai),求直线(等差数列就是直线呐)b=Ax+B使得式子最小的变化参数A,B,最后求误差式子的值
代码
#include
#include
using namespace std;
typedef long long LL;
typedef double ld;
const int N=1e5+10;
int a[N];
void solve() {
int n;
scanf("%d",&n);
ld x_average=0,y_average=0;//平均值
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
y_average+=a[i];
x_average+=i;
}
y_average/=n,x_average/=n;
ld A=0,B=0;
for(int i=1;i<=n;i++)
A+=(i-x_average)*(a[i]-y_average),
B+=(i-x_average)*(i-x_average);
A/=B;
B=y_average-A*x_average;//求得最优变换参数A,B
ld ans=0;
for(int i=1;i<=n;i++)
ans+=(A*i+B-a[i])*(A*i+B-a[i]);//差值答案
printf("%.15lf\n",ans);
}
int main() {
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
题意
给定一个长度为n的括号序列a,求长度为m的合法括号序列b,同时满足a是b的子序列的序列b的数量
题解
动态规划
定义:f[i,j,k]指b序列前i位中匹配了a序列的前j位,同时b序列中的净左括号数量为k
状态转移:
b[i]放’(‘。(注意:k需要大于0,否则数组越界)
a[j]=‘(’,即刚好两序列的最后一位匹配,那么数量在此情况下等于b序列前i-1位还需匹配a序列前j-1位同时有k-1个括号数量的序列数量
f
[
i
,
j
,
k
]
+
=
f
[
i
−
1
,
j
−
1
,
k
−
1
]
f[i,j,k]+=f[i-1,j-1,k-1]
f[i,j,k]+=f[i−1,j−1,k−1]
a[j]=‘)’,即b第i位与a第j位不匹配,那么此时数量等于b序列前i-1位需要匹配a序列的前j位,同时左括号数量为k-1的序列数量
f
[
i
,
j
,
k
]
+
=
f
[
i
−
1
,
j
,
k
−
1
]
f[i,j,k]+=f[i-1,j,k-1]
f[i,j,k]+=f[i−1,j,k−1]
b[i]放’)’
a[j]=‘(’,即b第i位与a第j位不匹配,那么此时数量等于b序列前i-1位需要匹配a序列的前j位,同时左括号数量为k+1的序列数量
f
[
i
,
j
,
k
]
+
=
f
[
i
−
1
,
j
,
k
+
1
]
f[i,j,k]+=f[i-1,j,k+1]
f[i,j,k]+=f[i−1,j,k+1]
a[j]=‘)’,即刚好两序列的最后一位匹配,那么数量在此情况下等于b序列前i-1位还需匹配a序列前j-1位同时有k+1个括号数量的序列数量
f
[
i
,
j
,
k
]
+
=
f
[
i
−
1
,
j
−
1
,
k
+
1
]
f[i,j,k]+=f[i-1,j-1,k+1]
f[i,j,k]+=f[i−1,j−1,k+1]
注意初始化,f[0,0,0]客观存在所以赋值为1。最终答案为f[m,n,0]
#include
#include
#include
using namespace std;
typedef long long LL;
const int N=210,MOD=1e9+7;
char s[N];
LL f[N][N][N];
void solve() {
int n,m;
scanf("%d%d%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;
for(int i=1;i<=m;i++)
for(int j=0;j<=min(i,n);j++)//优化
for(int k=0;k<=i;k++) {
//这样写编译就会爆数组越界
//if(k) f[i][j][k]+=f[i-1][j-(s[j]=='(')][k-1] %= MOD;
//f[i][j][k]+=f[i-1][j-(s[i]==')')][k+1] %= MOD;
if(k) {//b[i]填'(',注意k需要>0,否则数组越界
if(s[j]=='(')f[i][j][k]+=f[i-1][j-1][k-1];
else f[i][j][k]+=f[i-1][j][k-1];
f[i][j][k]%=MOD;
}
//b[i]填')'
if(s[j]==')') f[i][j][k]+=f[i-1][j-1][k+1];
else f[i][j][k]+=f[i-1][j][k+1];
f[i][j][k]%=MOD;
}
printf("%lld\n",f[m][n][0]);
}
int main() {
int T;
scanf("%d",&T);
while(T--) solve();
return 0;
}
题意
有n个世界,每个世界有若干个节点以及若干条有向边;游戏开始从点1出发,游戏中处于某点时,可以选择在这个世界内走向有边的点,也可以选择待着不动直接传送至下一个世界的同一点。问从点1走到m点所经过的最小世界数为多少?
题解
对于第i个世界可以从第i-1个世界走过来,答案也可以从上一世界的值转移过来,因此可以考虑dp
状态定义:
f[i,j]表示可以从1点走到第i个世界的j点的方案中,点1最晚出发的世界编号。由于内存不够,但每次转移只跟上一世界相关,所以滚动数组优化。now[x]表示从1点走到当前世界x点的方案中,点1最晚出发的世界编号。pre数组则表示上一世界的信息。
状态转移
对于第i个世界的某条边u->v
,可以从上一世界的u点转移过来
n
o
w
[
v
]
=
m
a
z
(
n
o
w
[
v
]
,
p
r
e
[
u
]
)
now[v]=maz(now[v],pre[u])
now[v]=maz(now[v],pre[u])
**注意:**当u为起点时,直接从当前世界走最优,如果不特殊处理使得答案更新错误
代码
#include
using namespace std;
const int INF = 0x3f3f3f3f;
int main()
{
ios::sync_with_stdio(0), cin.tie(0);
int n, m, ans = INF;//ans求最小值,直接初始化为无穷大
cin >> n >> m;
vector pre(m + 1), now(m + 1);
for (int i = 1; i <= n; i++)
{
int u, v, k; cin >> k;
now[1] = i;//在能从点1到达i世界中的1点的最晚世界编号为i
while (k--)
{
cin >> u >> v;//u->v有向边
now[v] = max(now[v], pre[u]);//向前面的世界转移
if (u == 1) now[v] = i;//如果边的起点为1,那么从1到世界i的点v的最晚世界编号就是i本身
}
if(now[m]) ans = min(ans, i-now[m]+1);//如果某个世界的到达点m的值被更新了,意味着能到点m,所以更新答案
pre = now;//滚动一下数组
}
if (ans == INF) ans = -1;//如果答案未被更新,意味着没有方案
cout << ans;
return 0;
}
题意
题解
代码
#include
#include
#include
#include
using namespace std;
const int N=1010,M=2010;
const double eps=1e-8;
int n,m;
int h[N],e[M],ne[M],idx;
double w[M];
void add(int a,int b,double c) {//a加上一条指向b的边,边权为c
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
int cnt[N];//记录每点到起点的经过边数
bool st[N];//某点是否在队列当中
double dis[N];//记录某点到起点的距离
bool spfa(double mid){//判断有无负环
queue q;
for(int i=1;i<=n;i++){
dis[i]=0;cnt[i]=0;//因为每次二分都进行一次spfa,一定初始化!
q.push(i);
st[i]=true;
}
while(!q.empty()){
int t=q.front();
q.pop();
st[t]=false;
for(int i=h[t];i!=-1;i=ne[i]){
int j=e[i];
if(dis[j]>dis[t]+w[i]+mid){
dis[j]=dis[t]+w[i]+mid;
cnt[j]=cnt[t]+1;
if(cnt[j]>=n)return true
if(!st[j]){
q.push(j);
st[j]=true;
}
}
}
}
return false;//所有可疑点都没有负环,所以没有负环
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin>>n>>m;
memset(h,-1,sizeof h);//初始化链表头
while(m--) {
int a,b,c,d;
cin>>a>>b>>c>>d;
add(b,d,-log(c*1.0/a));//加边以及负转换比率
}
double l=0,r=1;//二分系数w
while(r-l>eps) {
double mid=(l+r)/2;
if(spfa(-log(mid))) r=mid;//如果有负环意味着无限转换,意味着w太大,所以w应该在更小的范围找
else l=mid;
}
printf("%lf\n",r);
return 0;
}
题意
普通版的Nim游戏,但附加了条件:必胜者希望快速结束,必败者希望拖延
输出游戏进行的总轮数,以及先手第一轮可选择的方案数
题解
Nim游戏可得结论:异或总和为0时先手必败,否则必胜。
可证:输的人总能只取一个石子,并且使得对手下一轮也只能拿一个来拖延
先手必胜
先手第一轮应该取走尽可能多的石子maxv
,由结论剩下的轮数就是一人拿一个,所以游戏轮数为石子总和tot-max+1
,而可选方案就是所有可以取走maxv的堆数。
如何求解maxv?对于每一堆都去取最大量石子使得异或和为0(使对方转为必败态),取所有堆的最大值即可。
其中一堆的最大值如何求?假设我们取走之后剩余x个,最大值就是a[i]-x
那么相当于我们先拿走了a[i]个(suma[i]),再放进去x个(suma[i]x),要使得`sum^a[i]^x=0`,两边同异或suma[i]可以解得x=sum^a[i]
,所以某一堆最大值为a[i]-sum^a[i]
先手必败
先手第一轮就应该只拿一个,所以总轮数为石子总和tot,可选方案是所有堆中,先手拿走一个之后后手也只能拿一个,符合种情况的堆数。
如何统计堆数?首先把所有堆都拿走一个,并且记录拿走一个之后的总异或和,对于这种情况的异或和放入set并去重(即只保存拿走一个之后能到达的所有状态);再检验这些状态是否能让后手在下一轮只拿一个,不能就去掉这个不合法状态,那么set中剩下的就是先手拿走一个后使得后手只能拿一个的状态;最后检验所有堆在拿走一个后是否能到达set中的合法状态,记录合法的堆数,堆数即为可选方案。
代码
#include
#include
#include
using namespace std;
const int N=1e5+10;
#define int long long//爆int了,哭哭
int n,a[N];
void solve() {
cin>>n;
int sum=0,tot=0;//异或和,数量和
for(int i=0;i>a[i],sum^=a[i],tot+=a[i];
if(sum) {//先手必胜
int maxv=0,ans=0;//第一轮最大取走数量,先手第一轮方案数
for(int i=0;i S,T;
for(int i=0;i1) T.insert(SUM);//如果可取走不止一个,说明这个状态不最优,存入待删除的set
}
}
for(auto it:T) S.erase(it);//删除不合法状态
//以上预处理出了所有先手取走一个且后手也只能拿一个的状态
int ans=0;//记录方案数
for(int i=0;i>t;
while(t--) solve();
return 0;
}