海亮 7.18 杂题选讲
海亮 7.18 杂题
P1989 无向图三元环计数
Subtask 1 有三种做法,分别是枚举三个点
我们考虑给所有的边一个方向。具体的,如果一条边两个端点的度数不一样,则由度数较小的点连向度数较大的点,否则由编号较小的点连向编号较大的点。不难发现这样的图是有向无环的。注意到原图中的三元环一定与对应有向图中所有形如
假设存在环,按照我们连的有向边,那么应该满足 :
显然不会存在这种情况。
会不会存在这三个点的出度相同的情况呢?也不会,因为我们会从编号小的连向编号大的点。
那么该图就是一个
下面证明这个算法的时间复杂度是
首先我们可以在枚举
那么考虑对于每一条边
考虑分情况讨论。
- 当
在原图(无向图)上的度数不大于 时,由于新图每个节点的出度不可能大于原图的度数,所以 。 - 当
在原图上的度数大于 时,注意到它只能向原图中度数不小于它的点连边,又因为原图中所有的点的度数和为 ,所以原图中度数大于 的点只有 个。因此 的出边只有 条,也即 。
因此所有节点的出度均为
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e6 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , x[N] , y[N] , dag[N] , ans , vis[N];
int head[N] , cnt;
struct DQY { int to , nxt; } e[N];
void add ( int u , int v ) { e[++cnt] = { v , head[u] }; head[u] = cnt; }
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read();
for ( int i = 1 ; i <= m ; i ++ ) x[i] = read() , y[i] = read() , ++dag[x[i]] , ++dag[y[i]];
for ( int i = 1 ; i <= m ; i ++ )
{
int u = x[i] , v = y[i];
if ( dag[u] > dag[v] ) swap ( u , v );
else if ( dag[u] == dag[v] && u > v ) swap ( u , v );
add ( u , v );
}
for ( int u = 1 ; u <= n ; u ++ )
{
for ( int i = head[u] , v ; v = e[i].to , i ; i = e[i].nxt ) vis[v] = u;
for ( int i = head[u] , v ; v = e[i].to , i ; i = e[i].nxt )
for ( int j = head[v] ; j ; j = e[j].nxt )
if ( vis[e[j].to] == u ) ans ++;
}
cout << ans << endl;
return 0;
}
P6569 [NOI Online #3 提高组] 魔法值
首先 我们可以看到魔法值的定义:
那有了邻接矩阵
可以类比这个柿子(矩阵乘法):
那么我们如果将
这样就符合矩阵乘法的形式了
(实际上可以不调换定义 只调换乘法的顺序即可 具体见代码二)
那么我们将矩阵乘法改为"异或矩阵乘法"即可
它对于非
具体证明可以见这里
实际上我们可以将异或看成
对于初始值 是将所有值(第
对于多组询问 我们预处理
代码一:调换了
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e2 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , q;
struct DQY
{
int mat[N][N] , n , m;
void clear () { memset ( mat , 0 , sizeof mat ); }
friend DQY operator * ( DQY a , DQY b )
{
DQY res; res.clear();
res.n = a.n , res.m = b.m;
for ( int k = 1 ; k <= a.m ; k ++ )
for ( int i = 1 ; i <= a.n ; i ++ )
for ( int j = 1; j <= b.m ; j ++ )
res.mat[i][j] ^= a.mat[i][k] * b.mat[k][j];
return res;
}
}base , k[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , q = read();
base.clear() , base.n = 1 , base.m = n;
k[0].clear() , k[0].n = n , k[0].m = n;
for ( int i = 1 ; i <= n ; i ++ ) base.mat[1][i] = read();
for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , k[0].mat[u][v] = 1 , k[0].mat[v][u] = 1;
for ( int i = 1 ; i <= 31 ; i ++ ) k[i] = k[i-1] * k[i-1];
// for ( int i = 1 ; i <= n ; i ++ , cout.put('\n') , cout.put(endl) )
// for ( int j = 1 ; j <= k[i].n ; j ++ , cout.put(endl) )
// for ( int l = 1 ; l <= k[i].m ; l ++ )
// cout << k[i].mat[j][l] << ' ';
for ( int i = 1 ; i <= q ; i ++ )
{
int now = read();
DQY ans = base;
for ( int j = 0 ; j <= 31 ; j ++ )
if ( now & ( 1ll << j ) ) ans = ans * k[j];
cout << ans.mat[1][1] << endl;
}
return 0;
}
代码二:只需要改一下乘法的顺序即可(输入和乘法的时候略微做了修改)
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
const int N = 1e2 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , q;
struct DQY
{
int mat[N][N] , n , m;
void clear () { memset ( mat , 0 , sizeof mat ); }
friend DQY operator * ( DQY a , DQY b )
{
DQY res; res.clear();
res.n = a.n , res.m = b.m;
for ( int k = 1 ; k <= a.m ; k ++ )
for ( int i = 1 ; i <= a.n ; i ++ )
for ( int j = 1; j <= b.m ; j ++ )
res.mat[i][j] ^= a.mat[i][k] * b.mat[k][j];
return res;
}
}base , k[N];
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , q = read();
base.clear() , base.n = n , base.m = 1;
k[0].clear() , k[0].n = n , k[0].m = n;
for ( int i = 1 ; i <= n ; i ++ ) base.mat[i][1] = read();
for ( int i = 1 , u , v ; i <= m ; i ++ ) u = read() , v = read() , k[0].mat[u][v] = 1 , k[0].mat[v][u] = 1;
for ( int i = 1 ; i <= 31 ; i ++ ) k[i] = k[i-1] * k[i-1];
// for ( int i = 1 ; i <= n ; i ++ , cout.put('\n') , cout.put(endl) )
// for ( int j = 1 ; j <= k[i].n ; j ++ , cout.put(endl) )
// for ( int l = 1 ; l <= k[i].m ; l ++ )
// cout << k[i].mat[j][l] << ' ';
for ( int i = 1 ; i <= q ; i ++ )
{
int now = read();
DQY ans = base;
for ( int j = 0 ; j <= 31 ; j ++ )
if ( now & ( 1ll << j ) ) ans = k[j] * ans;
cout << ans.mat[1][1] << endl;
}
return 0;
}
P6190 [NOI Online #1 入门组] 魔法
设置
转移方程:
可以
然后将初始状态
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
#define int long long
const int N = 1e2 + 5;
const int M = 2500 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , m , K , u[M] , v[M] , w[M] , edge[M][M];
struct DQY
{
int mat[N][N];
DQY() { memset ( mat , 0x3f , sizeof mat ); }
friend DQY operator * ( DQY a , DQY b )
{
DQY ret;
for ( int k = 1 ; k <= n ; k ++ )
for ( int i = 1 ; i <= n ; i ++ )
for ( int j = 1 ; j <= n ; j ++ )
ret.mat[i][j] = min ( ret.mat[i][j] , a.mat[i][k] + b.mat[k][j] );
return ret;
}
}f,res;
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , m = read() , K = read();
for ( int i = 1 ; i <= m ; i ++ ) u[i] = read() , v[i] = read() , w[i] = read() , f.mat[u[i]][v[i]] = w[i] , edge[u[i]][v[i]] = w[i];
for ( int i = 1 ; i <= n ; i ++ ) f.mat[i][i] = 0;
for ( int k = 1 ; k <= n ; k ++ ) for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) f.mat[i][j] = min ( f.mat[i][j] , f.mat[i][k] + f.mat[k][j] );
if ( !K ) { cout << f.mat[1][n] << endl; return 0; }
for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) res.mat[i][j] = f.mat[i][j];
for ( int k = 1 ; k <= m ; k ++ ) for ( int i = 1 ; i <= n ; i ++ ) for ( int j = 1 ; j <= n ; j ++ ) f.mat[i][j] = min ( f.mat[i][j] , res.mat[i][u[k]] + res.mat[v[k]][j] - edge[u[k]][v[k]] );
while ( K )
{
if ( K & 1 ) res = res * f;
f = f * f , K >>= 1;
}
cout << res.mat[1][n] << endl;
return 0;
}
Piotr's Ants
是下面一道题的前置题捏(虽然不在题单里)
显然有一个结论:无论如何动 从左到右的编号序列是不变的
所以我们记录一个
注意两组数据之间要有一个空行
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N = 1e4 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int L , timer , n , now[N] , rk[N];
char ch;
struct DQY { int pos , fx , id; } st[N] , ed[N];
string s[3] = { "L" , "Turning" , "R" };
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
int T = read();
for ( int cases = 1 ; cases <= T ; cases ++ )
{
L = read() , timer = read() , n = read();
for ( int i = 1 , pos , fx ; i <= n ; i ++ )
{
pos = read() , cin >> ch;
fx = ( ch == 'L' ) ? -1 : 1;
st[i] = { pos , fx , i };
ed[i] = { pos + fx * timer , fx , i };
}
sort ( st + 1 , st + n + 1 , [](const DQY &a , const DQY &b) { return a.pos < b.pos; } );
sort ( ed + 1 , ed + n + 1 , [](const DQY &a , const DQY &b) { return a.pos < b.pos; } );
for ( int i = 1 ; i <= n ; i ++ ) rk[st[i].id] = i;
for ( int i = 1 ; i < n ; i ++ ) if ( ed[i].pos == ed[i+1].pos ) ed[i].fx = ed[i+1].fx = 0;
cout << "Case #" << cases << ':' << endl;
for ( int i = 1 ; i <= n ; i ++ )
if ( ed[rk[i]].pos < 0 || ed[rk[i]].pos > L ) cout << "Fell off" << endl;
else cout << ed[rk[i]].pos << ' ' << s[ed[rk[i]].fx+1] << endl;
cout << endl;
}
return 0;
}
P5835 [USACO19DEC] Meetings S
首先考虑一个结论 无论奶牛怎么走 整个序列从左到右的体重序列一定是不变的
因为我们两只奶牛相交的时候相当于是奶牛不变 交换体重
设之前轻的在左 重的在右 那么我们交换体重之后 轻的变成重的 继续向右走(这时它已经在右面了) 反之亦然
所以显然体重序列不变
考虑到时间具有单调性 即时间越多 奶牛一定越能走到头 所以可以二分答案
其他套路跟上一题基本类似
特别注意输入的时候
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
const int N = 1e5 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int L , timer , n , now[N] , rk[N] , rev[N] , sum , ans , wei[N];
//这里的wei需要单独开一个数组 不能放在结构体中 因为我们在用rev索引的时候 放在结构体中的wei已经打乱顺序了()
char ch;
struct DQY { int pos , fx , id; friend bool operator < ( const DQY a , const DQY b ) { return a.pos == b.pos ? a.fx < b.fx : a.pos < b.pos; } } a[N] , temp[N] , f[N];
int check ( int x )
{
int res = 0;
for ( int i = 1 ; i <= n ; i ++ ) temp[i] = a[i] , temp[i].pos += temp[i].fx * x;
sort ( temp + 1 , temp + n + 1 );
for ( int i = 1 ; i <= n ; i ++ ) if ( temp[i].pos >= L || temp[i].pos <= 0 ) res += wei[rev[i]];
return res * 2 >= sum;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , L = read();
for ( int i = 1 ; i <= n ; i ++ ) wei[i] = read() , a[i].pos = read() , a[i].fx = read() , a[i].id = i , sum += wei[i];
sort ( a + 1 , a + n + 1 );
for ( int i = 1 ; i <= n ; i ++ ) rk[a[i].id] = i , rev[i] = a[i].id;
int l = 0 , r = 1e9;
while ( l <= r )
{
if ( check(mid) ) r = mid - 1;
else l = mid + 1;
}
for ( int i = 1 ; i <= n ; i ++ ) temp[i] = a[i] , temp[i].pos += temp[i].fx * l;
sort ( temp + 1 , temp + n + 1 );
for ( int i = 1 ; i <= n ; i ++ ) if ( temp[i].fx == 1 ) ans += i - rk[temp[i].id];
cout << ans << endl;
return 0;
}
Tests for problem D
构造题 我们先
再按照逆序提取出来子区间 倒序赋值右区间 这样可以保证一个父亲节点的所有子区间是有包含关系的
先赋值
最后不要忘了处理根节点的右端点
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
const int N = 1e6 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , l[N] , r[N] , idx;
int head[N] , cnt;
struct DQY { int to , nxt; } e[N];
void add ( int u , int v ) { e[++cnt] = { v , head[u] } , head[u] = cnt; }
int dfs ( int u , int fa )
{
vector<int> vec;
for ( int i = head[u] , v ; v = e[i].to , i ; i = e[i].nxt ) if ( v != fa ) vec.push_back(v) , dfs ( v , u );
l[u] = ++idx;
while ( !vec.empty() ) r[vec.back()] = ++idx , vec.pop_back();
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read();
for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , add ( u , v ) , add ( v , u );
dfs ( 1 , 0 );
r[1] = ++idx;
for ( int i = 1 ; i <= n ; i ++ ) cout << l[i] << ' ' << r[i] << endl;
return 0;
}
You Are Given a Tree
观察到
对于小于
否则直接二分答案 二分答案为
将这些位置都赋值为这个答案 二分
那么可以证明整体复杂度在
我们可以按照叶子节点在前的
只要能组合成大于
那么我们可以向父亲上推 进行
以为是被卡常了 原来是循环的递增条件写错了()
#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define mid (l+r>>1)
const int N = 1e5 + 5;
int read ()
{
int x = 0 , f = 1;
char ch = cin.get();
while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = cin.get(); }
while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = cin.get(); }
return x * f;
}
int n , B , ans[N] , fa[N] , f[N] , dfn[N] , timer;
vector<int> e[N];
void dfs ( int u , int ff )
{
fa[u] = ff;
for ( auto v : e[u] )
if ( v != ff ) dfs ( v , u );
dfn[++timer] = u;
}
int solve ( int k )
{
int res = 0;
for ( int i = 1 ; i <= n ; i ++ ) f[i] = 1;
for ( int i = 1 ; i <= n ; i ++ )
{
int x = dfn[i];
if ( fa[x] && f[fa[x]] != -1 && f[x] != -1 )
{
if ( f[x] + f[fa[x]] >= k ) res ++ , f[fa[x]] = -1;
else f[fa[x]] = max ( f[fa[x]] , f[x] + 1 );
}
}
return res;
}
signed main ()
{
ios::sync_with_stdio(false);
cin.tie(0) , cout.tie(0);
n = read() , B = sqrt ( n * log2(n) );
for ( int i = 1 , u , v ; i < n ; i ++ ) u = read() , v = read() , e[u].push_back(v) , e[v].push_back(u);
dfs ( 1 , 0 ) , ans[1] = n;
for ( int i = 2 ; i <= B ; i ++ ) ans[i] = solve(i);
for ( int i = B + 1 , l , r , res ; i <= n ; i = r + 1 )
{
l = i , r = n , res = solve(l);
while ( l <= r )
{
if ( solve(mid) == res ) l = mid + 1;
else r = mid - 1;
}
for ( int j = i ; j <= r ; j ++ ) ans[j] = res;
}
for ( int i = 1 ; i <= n ; i ++ ) cout << ans[i] << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)