51nod验题
T1
我们发现一对 \(s\) 之间的公共部分就是这个位置的数,所以我们直接离散化之后在每一对 \(s\) 之间连边然后跑欧拉路就可以。
比较水,数据也比较水。Delov使用了当前弧优化造数据把所有按着板子贺的欧拉路径卡成了45分。
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
int n,cnt,lsh[200010],a[200010],d[200010];
bool v[200010];
struct node{
int a,b;
}s[200010];
struct stu{
int v,next;
bool vis;
}edge[400010];
int t,head[200010];
void add(int u,int v){
edge[t].v=v;edge[t].next=head[u];head[u]=t;
edge[t].vis=false;t++;
}
void dfs(int x){
for(int &i=head[x];~i;i=edge[i].next){//对就是这个网络流当前弧优化就是加个取地址
if(!edge[i].vis){
edge[i].vis=edge[i^1].vis=true;
dfs(edge[i].v);
}
}
a[++a[0]]=x;
}
signed main(){
scanf("%d",&n);
memset(head,-1,sizeof(head));
memset(edge,-1,sizeof(edge));
for(int i=1;i<n;i++){
scanf("%d",&s[i].a);lsh[++cnt]=s[i].a;
}
for(int i=1;i<n;i++){
scanf("%d",&s[i].b);lsh[++cnt]=s[i].b;
if(s[i].a>s[i].b){
printf("-1");return 0;
}
}
sort(lsh+1,lsh+cnt+1);
cnt=unique(lsh+1,lsh+cnt+1)-lsh-1;
for(int i=1;i<n;i++){
s[i].a=lower_bound(lsh+1,lsh+cnt+1,s[i].a)-lsh;
s[i].b=lower_bound(lsh+1,lsh+cnt+1,s[i].b)-lsh;
add(s[i].a,s[i].b);add(s[i].b,s[i].a);
d[s[i].a]++;d[s[i].b]++;
}
int st=1,ans=0;
for(int i=1;i<=cnt;i++)if(d[i]&1){
st=i;ans++;
}
if(ans!=0&&ans!=2){
printf("-1");return 0;
}
dfs(st);
if(a[0]!=n){
printf("-1");return 0;
}
for(int i=a[0];i>0;i--)printf("%d ",lsh[a[i]]);
}
T2
joke3579和T2杠上了。
信队算法比较容易想到,由于正环必定可以从一个地方出发,遍历环上所有点距离都是正数,很容易剪枝(原题能70)。
正解考虑一个倍增floyd, \(a[k][i][j]\) 为经过 \(2^k\) 条边后 \(i,j\) 间的最长路,然后设一个 \(g[i][j]\) 拿来转移,倍增跳到最大的不超过答案的值最后 \(+1\) 就行了。
我代码被joke3579重边和自环卡掉了(这位还扬言要卡常卡死我)所以上个验题人std。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5e2+10;
int n, m;
struct matrix{
int data[N][N];
matrix(int opt_ = 0){
init(opt_);
}
void init(int opt){
memset(data, 0xc0, sizeof(data));
if(opt == 1){
for(int i = 1; i<= n; ++i){
data[i][i] = 0;
}
}
}
friend matrix operator *(const matrix& a, const matrix&b){
matrix c;
for(int i = 1; i <= n; ++i){
for(int k = 1; k <= n; ++k){
for(int j = 1; j <= n; ++j){
c.data[i][j] = max(c.data[i][j], a.data[i][k] + b.data[k][j]);
}
}
}
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
c.data[i][j] = max({c.data[i][j], a.data[i][j], b.data[i][j]});
}
}
return c;
}
int * operator [](int x){
return data[x];
}
const int* operator[](int x)const{
return data[x];
}
void prt(){
for(int i = 1; i <= n; ++i){
for(int j = 1; j <=n ; ++j){
cerr<<data[i][j] <<" ";
}
cerr<<endl;
}
}
bool check(){
for(int i = 1; i <= n; ++i) {
if(data[i][i] > 0) return true;
}
return false;
}
}mp, f[16], tmp;
int RG = 13;
int main(){
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; ++i){
int u, v, w1, w2;
cin >> u >> v >> w1 >> w2;
mp[u][v] = max(mp[u][v], w1);
mp[v][u] = max(mp[v][u], w2);
}
f[0] = mp;
for(int i = 1; i <= RG ; ++i){
f[i] = f[i-1] * f[i-1];
}
if(!f[RG].check()){
cout << 0 <<endl;
return 0;
}
int ans = 0;
tmp.init(1);
for(int i = RG; i >= 0; --i){
if(!(tmp * f[i]).check()){
tmp = tmp * f[i];
ans = ans + (1 << i);
}
}
cout << ans + 1<< endl;
return 0;
}
您们觉得我和joke3579谁码风比较好?
T3
\(2\sqrt 2\) 题,考场上样例都模不出来。考后问了数据人嘉然小姐然后发现一次修水管可以一个也不爆就模出来了。
不会。还没改。
T4
这题原题是那个最远点对。我考场上调了半天被卡常然后愤而写了个 \(O(1)\) LCA过了。考后 \(huge\) 说要换掉于是想起来原先看过有这么个题于是花点时间切了。以下是题解。
原题:CF704B。
因为有 \(O(n\log n)\) 的做法,所以我把数据范围开到了 \(10^6\) 。
但是由于我只会 \(O(n^2)\) 的,所以我给了部分分 \(70\) 。
下面是题解。
Part 1.10pt
使用著名的信队算法中的 \(\text{next_permutation}\) 。
Part 2.30pt
不会。如果有会的可以联系一下补上。
Part 3.70pt
考虑dp。
曾经dp搬运工系列有一些关于若干连续段的dp,很神。这个差不多。而且本来想就开到 \(5000\) 算了的。
我们从小到大加入每个数字,然后套路设 \(dp[i][j]\) 为当前填入 \(i\) ,填完了 \(j\) 个连续段的答案。根据这一类题的套路,有三种情况:
- 新增一段,由于之后的所有数都比 \(i\) 大,所以这个数的贡献为 \(-x_i+b_i-x_i+d_i\) 。
- 延长一段,这要分类一下。如果延长一段左侧,那么左边比他大,右边比它小,贡献是 \(-x_i+b_i+x_i+c_i=b_i+c_i\) 。如果延长一段右侧,那么左边比他小,右边比它大,贡献是 \(x_i+a_i-x_i+d_i=a_i+d_i\) 。
- 合并两段,左右都比它小,那么贡献是 \(x_i+c_i+x_i+d_i\) 。
然后是一些不合法状态的考虑:
- 新增一段,随便搞。
- 延长一段,如果填了 \(s\) 且只有一段那么不能延长左侧,如果填了 \(t\) 且只有一段那么不能延长右侧。
- 合并两段,随便搞。
- 如果填入 \(s\) ,那么没有左边比它大的贡献且只有两种情况:延长左侧或者在左边新增一段。同理,如果填入 \(t\) ,那么没有右边比它大的贡献且只能延长右侧或者在右边新增一段。
代码应该还算好写?反正我没写(
Part 4.100pt
我们可以将每个位置的贡献分成四类讨论:(设当前位置为 \(i\) )
- 从更小的到 \(i\) : \(A_i=x_i+a_i\) 。
- 从更大的到 \(i\) : \(B_i=-x_i+b_i\) 。
- 从 \(i\) 到更小的: \(C_i=x_i+c_i\) 。
- 从 \(i\) 到更大的: \(D_i=-x_i+d_i\) 。
继续观察每个数的贡献变化:假设我们将 \(i\) 插入 \(x \rightarrow y\) ,则它们的贡献变化为:
- \(x>y\) ,则 \(y\) 的变化不变, \(x\) 从更小到更大,贡献增加 \(D_x-C_x\) 。
- \(x<y\) ,则 \(x\) 的变化不变, \(y\) 从更小到更大,贡献增加 \(B_y-A_y\) 。
所以我们的操作就只剩下了:
- 答案加上固定的一份 \(A_i+C_i\) .
- 找到最小的一个贡献加上然后把它去掉。
- 加入 \(i\) 左右的两份贡献。
直接把所有东西都扔进一个堆里就行。
然而还有一个问题,我们这样做要在一开始就插入 \(s,t\) ,而之后插入的 \(i\) 不一定是最大值。我们可以这样处理:
- 首先强制插入 \(1\) ,这样保证后来所有插入的数最多只有一侧不合法。
- 如果 \(i<s\) ,则我们先插入左侧的贡献 \(B_i-A_i\) ,然后再进行查询。首先这样做的话 \(B_i\) 没有问题,主要是 \(A_i\) 的问题。而 \(A_i\) 有问题当且仅当左边是 \(s\) 。而如果我们从堆中找到了 \(B_i-A_i\) ,那么和开始加上的固定的 \(A_i\) 兑掉,贡献就是 \(B_i\) ,而且之后因为只会插入更大的所以不会改变。如果没有找到的话那就没有任何关系。
- 如果 \(i<t\) 同样处理右边。
这样我们就将复杂度减小到了 \(O(n\log n)\) 。
这题答案最高是15位,而且我夏姬八随的数据的所有极限数据答案开头好像都是几个6。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <queue>
#define int long long
using namespace std;
int n,s,t,ans,x[1000010],a[1000010],b[1000010],c[1000010],d[1000010],nxt[1000010];
priority_queue<int,vector<int>,greater<int> >q;
int get(int i,int j){
return i>j?x[i]-x[j]+c[i]+b[j]:x[j]-x[i]+d[i]+a[j];
}
signed main(){
scanf("%lld%lld%lld",&n,&s,&t);nxt[s]=t;
for(int i=1;i<=n;i++)scanf("%lld",&x[i]);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
for(int i=1;i<=n;i++)scanf("%lld",&c[i]);
for(int i=1;i<=n;i++)scanf("%lld",&d[i]);
if(s==1||t==1)ans=get(s,t);
else ans=get(s,1)+get(1,t);
for(int i=2;i<=n;i++){
if(i!=s&&i!=t){
ans+=a[i]+c[i]+x[i]+x[i];
if(i<s)q.push(-x[i]-x[i]+b[i]-a[i]);
if(i<t)q.push(-x[i]-x[i]+d[i]-c[i]);
ans+=q.top();q.pop();
if(i>s)q.push(-x[i]-x[i]+b[i]-a[i]);
if(i>t)q.push(-x[i]-x[i]+d[i]-c[i]);
}
else if(i==s)q.push(-x[i]-x[i]+d[i]-c[i]);
else q.push(-x[i]-x[i]+b[i]-a[i]);
}
printf("%lld",ans);
}