昨天UNR考完之后,有许多小朋友问我标程怎么写的owo
那我挂一下好了
//Awwawa! Dis cold yis ratten buy tEMMIE!
#include <bits/stdc++.h>
#define ll long long
#define maxn 100005 /*rem*/
#define mod 998244353
#define db double
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define pi pair<int, int>
#define fi first
#define se second
template <typename T> bool chkmax(T &x,T y){return x<y?x=y,true:false;}
template <typename T> bool chkmin(T &x,T y){return x>y?x=y,true:false;}
using namespace std;
ll ksm(ll a, ll b) {
if (!b) return 1;
ll ns = ksm(a, b >> 1);
ns = ns * ns % mod;
if (b & 1) ns = ns * a % mod;
return ns;
}
int out[maxn], in[maxn];
vector<pi> eg;
mt19937 x;
int gt(int a, int b) {
if (a == b) return 1;
if (!a) return 0;
return gt(out[a], b);
}
int main() {
for (int t = 1/*1*/; t <= 10; t++) {
char nm[30];
sprintf(nm, "hamil%d.in", t);
ifstream rd(nm);
sprintf(nm, "hamil%d.ans", t);
cout << "WORK" << t << endl;
ofstream ot(nm);
int n, m;
rd >> n >> m;
eg.clear();
for (int i = 1; i <= m; i++) {
int u, v;
rd >> u >> v;
eg.pb(mp(u, v));
}
shuffle(eg.begin(), eg.end(), x);
while (1) {
int tot = 0, cnt = 0;
memset(out, 0, sizeof(out));
memset(in, 0, sizeof(in));
while (1) {
int fl = 0;
shuffle(eg.begin(), eg.end(), x);
for (auto v : eg) {
if (in[v.se] && out[v.fi]) continue;
if (gt(v.se, v.fi)) continue;
if (in[v.se] || out[v.fi])
if (x() & 1) continue;
if (!in[v.se] && !out[v.fi])
tot++, fl = 1;
if (in[v.se]) {
out[in[v.se]] = 0;
in[v.se] = 0;
}
if (out[v.fi]) {
in[out[v.fi]] = 0;
out[v.fi] = 0;
}
in[v.se] = v.fi;
out[v.fi] = v.se;
}
if (tot == n - 1) break;
if (fl) cnt = 0;
else {
cnt++;
if (cnt >= 3000000) break;
}
if (x() % 20 == 0)cout << tot << endl;
}
if (tot == n - 1) break;
cout << "AGAIN" << endl;
}
for (int i = 1; i <= n; i++)
if (!in[i]) {
int pl = i;
while (pl)
ot << pl << ' ',
pl = out[pl];
break;
}
ot << endl;
}
return 0;
}
这是当时的标程w 由于我很懒,什么优化也没加
今天空闲时拷了个LCT的板子,又封装了一下,现在可以在4s跑完UNR4 D2T3
代码如下
//Awwawa! Dis cold yis ratten buy tEMMIE!
#include <bits/stdc++.h>
/*这是一份UNR4 D2T3示例代码 只需要几秒就能AC该题
hamil::work是封装好的函数,不需初始化可直接调用
其参数为: int n, vector<pair<int, int> > edges, int mx_ch = -1
其中n为有向图点数(从1开始标号),edges为有向边的集合(edges中元素pair<u, v>表示从u到v有向边),mx_ch为最大调整次数。如果不设初值,默认为(n+100)*(n+50)
这个函数时间复杂度不超过mx_ch * log(n) 如果能找到哈密顿链,往往远快于这个上界
如果有哈密顿链,函数有较大概率返回一条。链经过的节点按照经过的顺序储存在返回的vector<int> 中。
如果函数未能找到,会返回空的vector<int>。这时可以调大mx_ch阈值,或者多试几次w
欢迎大家喂各种图调戏它x
*/
using namespace std;
namespace hamil {
template <typename T> bool chkmax(T &x,T y){return x<y?x=y,true:false;}
template <typename T> bool chkmin(T &x,T y){return x>y?x=y,true:false;}
#define vi vector<int>
#define pb push_back
#define mp make_pair
#define pi pair<int, int>
#define fi first
#define se second
#define ll long long
namespace LCT {
vector<vi> ch;
vi fa, rev;
void init(int n) {
ch.resize(n + 1);
fa.resize(n + 1);
rev.resize(n + 1);
for (int i = 0; i <= n; i++)
ch[i].resize(2),
ch[i][0] = ch[i][1] = fa[i] = rev[i] = 0;
}
bool isr(int a)
{
return !(ch[fa[a]][0] == a || ch[fa[a]][1] == a);
}
void pushdown(int a)
{
if(rev[a])
{
rev[ch[a][0]] ^= 1, rev[ch[a][1]] ^= 1;
swap(ch[a][0], ch[a][1]);
rev[a] = 0;
}
}
void push(int a)
{
if(!isr(a)) push(fa[a]);
pushdown(a);
}
void rotate(int a)
{
int f = fa[a], gf = fa[f];
int tp = ch[f][1] == a;
int son = ch[a][tp ^ 1];
if(!isr(f))
ch[gf][ch[gf][1] == f] = a;
fa[a] = gf;
ch[f][tp] = son;
if(son) fa[son] = f;
ch[a][tp ^ 1] = f, fa[f] = a;
}
void splay(int a)
{
push(a);
while(!isr(a))
{
int f = fa[a], gf = fa[f];
if(isr(f)) rotate(a);
else
{
int t1 = ch[gf][1] == f, t2 = ch[f][1] == a;
if(t1 == t2) rotate(f), rotate(a);
else rotate(a), rotate(a);
}
}
}
void access(int a)
{
int pr = a;
splay(a);
ch[a][1] = 0;
while(1)
{
if(!fa[a]) break;
int u = fa[a];
splay(u);
ch[u][1] = a;
a = u;
}
splay(pr);
}
void makeroot(int a)
{
access(a);
rev[a] ^= 1;
}
void link(int a, int b)
{
makeroot(a);
fa[a] = b;
}
void cut(int a, int b)
{
makeroot(a);
access(b);
fa[a] = 0, ch[b][0] = 0;
}
int fdr(int a)
{
access(a);
while(1)
{
pushdown(a);
if (ch[a][0]) a = ch[a][0];
else {
splay(a);
return a;
}
}
}
}
vi out, in;
vi work(int n, vector<pi> eg, ll mx_ch = -1) { // mx_ch : 最大调整次数 如果不设初值,默认为(n + 100) * (n + 50)
// 如果存在,有较大概率返回一条路径
// 如果失败 返回0
out.resize(n + 1), in.resize(n + 1);
LCT::init(n);
for (int i = 0; i <= n; i++) in[i] = out[i] = 0;
if (mx_ch == -1) mx_ch = 1ll * (n + 100) * (n + 50); //时间上限
vector<vi> from(n + 1), to(n + 1);
for (auto v : eg)
from[v.fi].pb(v.se),
to[v.se].pb(v.fi);
unordered_set<int> canin, canout;
for (int i = 1; i <= n; i++)
canin.insert(i),
canout.insert(i);
mt19937 x(time(0));
int tot = 0;
while (mx_ch >= 0) {
// cout << tot << ' ' << mx_ch << endl;
vector<pi> eg;
for (auto v : canout)
for (auto s : from[v])
if (in[s] == 0) {
assert(canin.count(s));
continue;
}
else eg.pb(mp(v, s));
for (auto v : canin)
for (auto s : to[v])
eg.pb(mp(s, v));
shuffle(eg.begin(), eg.end(), x);
if (eg.size() == 0) break;
for (auto v : eg) {
mx_ch--;
if (in[v.se] && out[v.fi]) continue;
if (LCT::fdr(v.fi) == LCT::fdr(v.se)) continue;
if (in[v.se] || out[v.fi])
if (x() & 1) continue;
if (!in[v.se] && !out[v.fi])
tot++;
if (in[v.se]) {
LCT::cut(in[v.se], v.se);
canin.insert(v.se);
canout.insert(in[v.se]);
out[in[v.se]] = 0;
in[v.se] = 0;
}
if (out[v.fi]) {
LCT::cut(v.fi, out[v.fi]);
canin.insert(out[v.fi]);
canout.insert(v.fi);
in[out[v.fi]] = 0;
out[v.fi] = 0;
}
LCT::link(v.fi, v.se);
canin.erase(v.se);
canout.erase(v.fi);
in[v.se] = v.fi;
out[v.fi] = v.se;
}
if (tot == n - 1) {
vi cur;
for (int i = 1; i <= n; i++)
if (!in[i]) {
int pl = i;
while (pl) {
cur.pb(pl),
pl = out[pl];
}
break;
}
return cur;
}
}
//fail to find a path
return vi();
}
}
int main() {
for (int t = 1; t <= 10; t++) {
char nm[30];
sprintf(nm, "hamil%d.in", t);
ifstream rd(nm);
sprintf(nm, "hamil%d.out", t);
cout << "WORK" << t << endl;
ofstream ot(nm);
int n, m;
rd >> n >> m;
vector<pi> eg;
for (int i = 1; i <= m; i++) {
int u, v;
rd >> u >> v;
eg.pb(mp(u, v));
}
l1:
vi ed = hamil::work(n, eg);
if (!ed.size()) goto l1;
ot << ed.size() << endl;
for (auto v : ed)
ot << v << ' ';
ot << endl;
}
return 0;
}
一些附加说明:
如果给定起点终点,只需新建两个点$U,V$ ; 然后 $U$ 到起点连一条边,终点到 $V$ 连一条边
如果要求哈密顿圈,可以枚举一条边,转化成给定起点终点情形。
如果是无向图,只需正反边各加一遍即可。