好久没有这么啃一份代码了,感觉网上这题的代码都是dls的各种改版2333
先说一下我对dls思路的理解吧:这道题问的是最少需要多少一笔画,那么就可以这么想:把这个图加最少的边完善成可以一笔画的图,然后跑欧拉路径就好,如果遇到新加的边,那么就说明这是一个新的一笔画。而加边的过程依据的是欧拉路的性质——一对奇数度的是可以的,再多的话就要连接一对奇数点让他们的度变成偶数。
#include
#include
#include
#include
using namespace std;
const int N = 100100;
struct node{
int v;
int next;
bool able;
}edge[N * 4];//要多于总边数的4倍(*2双向边 并且可能加边)
int head[N], cnt;
int n, m, res;//找到的路径数
bool vis[N];
int deg[N]; //点的度数
vector st; //保存一个联通块中度数为奇数的点
vector road[N];
void init(){
res = 0;
cnt = 1;
memset(vis, 0, sizeof(vis));
memset(head, 0, sizeof(head));
memset(deg, 0, sizeof(deg));
}
void add(int u, int v){
edge[++ cnt]. next = head[u];
edge[cnt]. able = true;
edge[cnt]. v = v;
head[u] = cnt;
deg[u] ++;
}
void add_Edge(int u, int v){
add(u, v);
add(v, u);
}
//找联通块以及度为奇数的点
void dfs1(int s){
vis[s] = true;
if(deg[s] & 1)
st. push_back(s);
for(int i = head[s]; i; i = edge[i]. next){
int v = edge[i]. v;
if(! vis[v])
dfs1(v);
}
}
void dfs2(int s){
for(int i = head[s]; i; i = edge[i]. next){
if(edge[i]. able){
edge[i]. able = edge[i ^ 1]. able = false; //将这条边以及它的反向边标记为已使用
dfs2(edge[i]. v);
if(i > 2 * m + 1) //说明此边是由奇数点添加得到,所以这条回路已经结束
res ++;
else
road[res]. push_back((i / 2) * (2 * (i & 1) - 1)); //确定边的序号以及正负,偶数为负,奇数为正,因为偶数边是反向边
}
}
}
int main(){
while(~ scanf("%d %d", &n, &m)){
init();
for(int i = 0; i < m; i ++){
int u, v;
scanf("%d %d", &u, &v);
add_Edge(u, v);
}
for(int i = 1; i <= n; i ++){
if(! vis[i] && deg[i]){
dfs1(i); //找到联通块和奇数度数的顶点
if(st. empty()){ //孤立点
st. push_back(i);
st. push_back(i);
}
for(int j = 2; j < st. size(); j += 2){ //从第二对开始的奇数顶点加一条边 ,因为如果只有一对奇数点,是可以完成欧拉路的
add_Edge(st[j], st[j + 1]);
}
res ++;
dfs2(st[0]);
st. clear();
}
}
printf("%d\n", res);
for(int i = 1; i <= res; i ++){
printf("%d", road[i]. size());
for(int j = 0; j < road[i]. size(); j ++){
printf(" %d", road[i][j]);
}
puts("");
road[i]. clear();
}
}
return 0;
}
一遇到构造题就有点懵,这题依然是。一开始都没搞清题目想让我干啥,被题面搞晕了
然后这份代码,emmm,用心体会。。
ps:dls直播中说的1的位置移动的解释:比如p = 5, 有这么一小组 10000, 1的位置是0,移动一1的位置就变成1,即01000
#include
using namespace std;
int grid[3010][3010];
int main(){
int p = 47;
for(int i = 0; i < p; i ++){
for(int j = 0; j < p; j ++){
for(int k = 0; k < p; k ++){
grid[i * p + j][k * p + (j * k + i) % p] = 1; //行列数 = n*n
}
}
}
puts("2000");
for(int i = 0; i < 2000; i ++){
for(int j = 0; j < 2000; j ++){
printf("%d", grid[i][j]);
}
puts("");
}
return 0;
}
神仙题。
感觉这道题给我开了一扇窗——配合容斥的组合计数。
这题看大佬的讲解就是各种懵,??容斥公式怎么来的,??复杂度从n^4直接降到n^2
#include
using namespace std;
typedef long long ll;
const int N = 3030;
const ll mod = 998244353;
int m, n, a, b;
int pw[N * N], wz[N][N], s[N][N], fac[N];
ll inv[N], ans;
void init(){
inv[0] = inv[1] = pw[0] = fac[1] = 1;
for(int i = 1; i < N * N; i ++)
pw[i] = pw[i - 1] * 2 % mod;
for(int i = 2; i < N; i ++)
inv[i] = (mod - mod / i) * inv[mod % i] % mod;
for(ll i = 2; i < N; i ++){
fac[i] = fac[i - 1] * i % mod;
inv[i] = inv[i - 1] * inv[i] % mod;
}
for(int i = 0; i < N; i ++){
for(int j = i; j >= 0; j --){
int delta = inv[j] * inv[i - j] % mod;
if((i - j) & 1)
delta = mod - delta;
s[i][j] = s[i][j + 1] + delta;
if(s[i][j] >= mod)
s[i][j] -= mod;
}
for(int j = 0; j < N; j ++)
wz[i][j] = pw[i * j] * inv[i] % mod * inv[j] % mod;
}
}
int main(){
init();
while(~scanf("%d %d %d %d", &n, &m, &a, &b)){
ans = 0;
for(int w = 0; w <= n - a; w ++)
for(int z = 0; z <= m - b; z++){
ans = ans + 1ll * s[n - w][a] * s[m - z][b] % mod * wz[w][z] % mod;
if(ans >= mod)
ans -= mod;
}
printf("%lld\n", ans * fac[n] % mod * fac[m] % mod);
}
return 0;
}
这就算是线段树比较高端的操作了吧,通过维护几个值来巧妙地完成题目的要求
ps:顺便扒了个区间维护的模板,以前自己敲的那份太残废了
#include
using namespace std;
#define Lson l, m, rt << 1
#define Rson m + 1, r, rt << 1 | 1
const int N = 1e5 + 5;
int n, q;
struct node{
int cnt, lazy, minb, maxa;
}tree[N << 2];
int b[N];
void push_up(int rt){
tree[rt]. minb = min(tree[rt << 1]. minb, tree[rt << 1 | 1]. minb);
tree[rt]. cnt = tree[rt << 1]. cnt + tree[rt << 1 | 1]. cnt;
tree[rt]. maxa = max(tree[rt << 1]. maxa, tree[rt << 1 | 1]. maxa);
}
void push_down(int rt){
if(tree[rt]. lazy){
int v = tree[rt]. lazy;
tree[rt]. lazy = 0;
tree[rt << 1]. maxa += v;
tree[rt << 1 | 1]. maxa += v;
tree[rt << 1]. lazy += v;
tree[rt << 1 | 1]. lazy += v;
}
}
void build(int l, int r, int rt){
tree[rt]. lazy = 0;
if(l == r){
tree[rt]. cnt = tree[rt]. maxa = 0;
tree[rt]. minb = b[l];
return;
}
int m = l + r >> 1;
build(Lson);
build(Rson);
push_up(rt);
}
void updata(int L, int R, int l, int r, int rt){
if(L <= l && r <= R){
tree[rt]. maxa ++;
if(tree[rt]. maxa < tree[rt].minb){ //分子的最大值小于分母的最小值,没必要急着向下维护,打上懒惰标记
tree[rt]. lazy ++;
return;
}
if(l == r && tree[rt]. maxa >= tree[rt]. minb){
tree[rt]. cnt ++;
tree[rt]. minb += b[l]; //最小值加上分母,相当于提高了+1的门槛,为下次+1做准备
return;
}
}
push_down(rt);
int m = l + r >> 1;
if(L <= m)
updata(L, R, Lson);
if(R > m)
updata(L, R, Rson);
push_up(rt);
}
int query(int L, int R, int l, int r, int rt){
if(L <= l && r <= R){ //到达这一步前有很多前置条件,使得一定可以完成对待查询区间的精确覆盖
return tree[rt]. cnt;
}
int m = l + r >> 1;
push_down(rt);
int ans = 0;
if(L <= m)
ans += query(L, R, Lson);
if(R > m)
ans += query(L, R, Rson);
return ans;
}
int main()
{
int n, x, y;
while(~ scanf("%d %d", &n, &q)){
for(int i = 1; i <= n; i ++){
scanf("%d", &b[i]);
}
build(1, n, 1);
char pp[6];
int l, r;
while(q --){
scanf("%s %d %d", pp, &l, &r);
if(pp[0] == 'a'){
updata(l, r, 1, n, 1);
}
else{
printf("%d\n", query(l, r, 1, n, 1));
}
}
}
return 0;
}
因为交换相邻的两个数一定可以减少一个逆序对,所以这题的关键就是如何快速求出逆序对,我这里的是归并的方法
#include
using namespace std;
typedef long long ll;
const ll N = 500010;
ll a[N], t[N], ans;
void Merge_sort(ll l, ll r){
if(l == r)
return ;
ll mid = (l + r) >> 1;
Merge_sort(l, mid); //得到左区间有序的数组
Merge_sort(mid + 1, r); //得到右区间有序的数组
ll i = l, j = mid + 1, now = 0;
while(i <= mid && j <= r){
if(a[i] > a[j]){//归并排序是稳定排序,所以不写>=
ans += (mid - i + 1); //mid + 1 到 j 都是有序的, i 到 mid 都是大于j的,共有mid - i + 1项,即逆序对的数目
t[++ now] = a[j ++];
}
else{
t[++ now] = a[i ++];
}
}
while(i <= mid)//(处理左右子区间中、指针未指到最后所剩下的元素)
t[++ now] = a[i ++];
while(j <= r)
t[++ now] = a[j ++];
now = 0;//(将两个子区间、经过有序+合并处理后得到的t[],赋值到对应位置的a[]中)
for(ll k = l; k <= r; k ++)
a[k] = t[++ now];
}
int main(){
ll n, x, y;
while(~ scanf("%lld %lld %lld", &n, &x, &y)){
ans = 0;
for(ll i = 1; i <= n; i ++)
scanf("%lld", &a[i]);
Merge_sort(1, n);
if(x < y)
printf("%lld\n", ans * x);
else
printf("%lld\n", ans * y);
}
return 0;
}