0%

7月1日 AtCoder Regular Contest 100 题目链接: https://arc100.contest.atcoder.jp/

C - Linear Approximation

思路: 首先看到那个式子比较复杂,其实可以将它化简,把第i项减去i。然后观察式子可以发现那个值就是序列的中位数,然后直接算出答案。其实这就是那个货仓选址的模型,比较重要。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <bits/stdc++.h>
#define LL long long
using namespace std;

int n;
LL ans, a[200005];

int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%lld", &a[i]), a[i]-=i;
sort(a+1, a+n+1);
LL t=a[(n+1)/2];
for(int i=1; i<=n; i++)
{
if(a[i]-t<0) ans+=t-a[i];
else ans+=a[i]-t;
}
printf("%lld\n", ans);
}

D - Equal Cut

思路: 玄学。。。首先可以枚举中间切的那一刀,由于我们希望极差最小,所以有一种感觉就是要分得尽量平均。因此对于中间的每一刀,我们可以确定出左边和右边两刀,把序列左边和右边切得更“平均”,用这个更新答案。其实有一点迷QWQ 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>
#define LL long long
#define INF 1e18
#define MAXN 200005
using namespace std;

int n;
LL a[MAXN], ans=INF;

int main()
{
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%lld", &a[i]), a[i]+=a[i-1];
int l=1, r=3;
LL maxn, minn;
for(int i=2; i<=n-2; i++)
{
while(a[l]<a[i]-a[l]) l++;
while(a[r]-a[i]<a[n]-a[r]) r++;
if(a[l-1]>a[i]-a[l]) l--;
if(a[r-1]-a[i]>a[n]-a[r]) r--;
maxn=max(max(a[l], a[i]-a[l]), max(a[r]-a[i], a[n]-a[r]));
minn=min(min(a[l], a[i]-a[l]), min(a[r]-a[i], a[n]-a[r]));
ans=min(ans, maxn-minn);
}
printf("%lld\n", ans);
}

E - Or Plus Max

思路: 发现自己位运算是真的菜。。。。

类似于一个DP。首先枚举下标0到1<<n,如果下标x的第i位上是0(二进制)那么它一定包含于(同样是二进制)下标 x&(1<<i)(也就是把那一位变为1)。所以可以用下标x的数来更新下标x&(1<<i)的数之前的最大值。 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <bits/stdc++.h>
#define MAXN 300005
#define LL long long
using namespace std;

int n, f[MAXN][2];
LL ans[MAXN], a[MAXN];

void solve(int x, int y)
{
if(f[x][0]==y || f[x][1]==y) return;
if(a[f[x][1]]<a[y])
{
f[x][1]=y;
if(a[f[x][0]]<a[f[x][1]]) swap(f[x][0], f[x][1]);
}
}

int main()
{
scanf("%d", &n);
for(int i=0; i<(1<<n); i++)
{
scanf("%lld", &a[i]);
f[i][0]=i;
}
for(int i=0; i<(1<<n); i++) for(int j=0; j<n; j++)
{
if((i>>j)&1) continue;
solve(i|(1<<j), f[i][0]);
solve(i|(1<<j), f[i][1]);
}
for(int i=1; i<(1<<n); i++)
{
ans[i]=max(ans[i-1], a[f[i][0]]+a[f[i][1]]);
printf("%lld\n", ans[i]);
}
}

6月24日 Lydsy1806月赛 题目链接:https://www.lydsy.com/JudgeOnline/contest.php?cid=1014

A - 字符串大师II

思路: 其实没有什么思路的。border数组就是是KMP中的next,然后就打表找一下规律,就发现了结论:$ans=p-2^{log2(k)}$。

但是要注意一点,当k很大时$log2(k)$会被卡精度。开一个数组sec记录$log2(k)$为多少就好了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define LL long long
using namespace std;

LL t, k, p, sec[100], temp;

int main()
{
scanf("%lld", &t);
sec[0]=1;
for(int i=1; i<=60; i++) sec[i]=sec[i-1]*2;
for(int i=1; i<=t; i++)
{
scanf("%lld%lld", &k, &p);
for(int j=1; j<=60; j++)
{
if(p>=sec[j-1] && p<sec[j])
{
temp=j-1;
break;
}
}
printf("%lld\n", p-((LL)1<<temp));
}
}

B - 超速摄像头

思路: 由于道路是树的结构,所以越外层节点数越多,因此我们尽量选外层的节点装摄像头。

然后直接模拟,所有入度为1的节点为最外层,然后删掉最外层节点并k-2。重复此操作直到k<=2,如果最后k=1时,则再可以给任意一个节点装摄像头。最后统计一下答案就好了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#define LL long long
#define rint register int
#define MAXN 1000005
using namespace std;

int n, k, ans, cnt, top1, top2, head[MAXN], deg[MAXN], sta[MAXN], temp[MAXN];
bool vis[MAXN];

struct Edge {int next, to;} edge[MAXN*2];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
deg[to]++;
head[from]=cnt;
}

int main()
{
int x, y;
scanf("%d%d", &n, &k);
if(k==1) {printf("1\n"); return 0;}
for(int i=1; i<n; i++)
{
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
for(int i=1; i<=n; i++) if(deg[i]==1) sta[++top1]=i, vis[i]=1;
k-=2;
while(k>=2 && top1>0)
{
for(int j=1; j<=top1; j++)
{
int x=sta[j];
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(vis[to]) continue;
deg[to]--;
if(deg[to]==1) temp[++top2]=to;
}
}
top1=top2;
for(int i=1; i<=top2; i++) sta[i]=temp[i], vis[temp[i]]=1;
k-=2; top2=0;
}
for(int i=1; i<=n; i++)
{
if(vis[i]) ans++;
else if(k>0) k--, ans++;
}
printf("%d\n", ans);
}

C - 质数拆分

思路: 比较暴力的枚举吧。设f[i]为两个质数和为i的方案数,通过枚举计算出f数组。答案就是i从1到n $f[i]*f[n-i]$的和

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <bits/stdc++.h>
using namespace std;

int n, tot, prime[150005], f[150005], check[150005];
long long ans;

int main()
{
scanf("%d", &n);
for(int i=2; i<=n; i++)
{
if(!check[i]) prime[++tot]=i;
for(int j=1; j<=tot && i*prime[j]<=n; j++)
{
check[i*prime[j]]=1;
if(i%prime[j]==0) break;
}
}
for(int i=1; i<=tot && prime[i]*2<=n; i++)
{
f[prime[i]*2]++;
for(int j=i+1; j<=tot && prime[i]+prime[j]<=n; j++) f[prime[i]+prime[j]]+=2;
}
for(int i=1; i<=n/2; ++i) ans+=f[i]*f[n-i]*2;
if(n%2==0) ans-=f[n/2]*f[n/2];
printf("%lld\n", ans);
}

E - 比例查询

思路: 特别好的一道题。对于这种序列问题,不能够区间合并,离线可能是一种做法。这道题的离线思想和BZOJ1878 HH的项链有点像。

设f[i]为数i最近的位置,g[i]为比例为i的两个数最近的位置。先将询问的右端点排序,然后从左向右扫。每扫到一个数找它的约数,用这个来跟新f[i],g[i],如果有询问的右端点在扫到的位置上,就根据g[i]回答询问。

但要注意,我们从左到右扫是是默认了右边的数为左边的数的b倍,因此我们需要再从右向左扫一遍。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <bits/stdc++.h>
#define MAXN 100005
using namespace std;

int n, m, a[MAXN], f[MAXN], g[MAXN], ans[MAXN];

struct Q {int l, r, b, pos;} q[MAXN];

bool CMP(Q x, Q y) {return x.r<y.r;}

void solve()
{
sort(q+1, q+m+1, CMP);
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
for(int i=1, j=1; i<=n; i++)
{
for(int k=1; k*k<=a[i]; k++)
{
if(a[i]%k) continue;
g[k]=max(g[k], f[a[i]/k]);
g[a[i]/k]=max(g[a[i]/k], f[k]);
}
f[a[i]]=i;
for(; q[j].r==i; j++) if(g[q[j].b]>=q[j].l) ans[q[j].pos]=1;
}
}

int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
for(int i=1; i<=m; i++)
{
scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].b);
q[i].pos=i;
}
solve();
reverse(a+1, a+n+1);
for(int i=1; i<=m; i++)
{
q[i].l=n-q[i].l+1;
q[i].r=n-q[i].r+1;
swap(q[i].l, q[i].r);
}
solve();
for(int i=1; i<=m; i++)
{
if(ans[i]) printf("Yes\n");
else printf("No\n");
}
}

G - 最长公共子序列

思路: 一道结论题,当T全为一个字母时是最优的,因此答案就是出现次数最少的字母的出现数。(凭感觉吧,不太会证明)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
using namespace std;

int num[30], ans=1e9, len;
char s[1000005];

int main()
{
scanf("%s", s);
len=strlen(s);
for(int i=0; i<len; i++) num[s[i]-'a']++;
for(int i=0; i<26; i++) ans=min(ans, num[i]);
printf("%d\n", ans);
}

问题描述:

简单来说,就是给你一棵树,在直径上找一个长度不超过s的链,使得偏心距最小。偏心距的定义为:树上到这条链最远的节点到这条链的距离。

输入:

包含n行: 第1行,两个正整数n和s。其中n为树网结点的个数,s为树网的核的长度的上界。 从第2行到第n行,每行给出3个用空格隔开的正整数,依次表示每一条边的两个端点编号和长度。

注:NOIP原题数据:n<=300,BZOJ加强数据:n<=500000

输出:

只有一个非负整数,为指定意义下的最小偏心距。

思路:

首先想NOIP数据范围的做法,因为我们找的链是在直径上。所以我们可以先处理出直径再暴力枚举直径上的链。因为链长要小于等于s,我们可以枚举一个端点,然后尽可能远的在链上找到另一个端点。这样我们就$O(n)$暴力枚举处理所有链,对于每一条链用一次dfs找出偏心距就OK了。

然后再考虑BZOJ加强版数据,枚举一个端点,然后尽可能远的在链上找到另一个端点。但是我们可以不需要对于每一条链都找出偏心距,我们可以利用直径的性质$O(n)$统一处理出答案。

代码:

$O(n^2)$暴力:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define MAXN 3005
#define INF 0x3f3f3f3f
using namespace std;

int n, s, root, cnt, ans=INF, ecc, head[MAXN], dis[MAXN], pre[MAXN];
bool vis[MAXN];

struct Edge {int next, to, dis;} edge[MAXN*2];

void addedge(int from, int to, int dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}

void bfs(int root)
{
queue<int> q;
vis[root]=true; q.push(root);
while(!q.empty())
{
int x=q.front(); q.pop();
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(vis[to]) continue;
pre[to]=x; vis[to]=true;
dis[to]=dis[x]+edge[i].dis;
q.push(to);
}
}
}

void getline()
{
int maxdis=0;
bfs(1);
for(int i=1; i<=n; i++) if(dis[i]>maxdis) maxdis=dis[i], root=i;
memset(vis, 0, sizeof(vis));
memset(pre, 0, sizeof(pre));
memset(dis, 0, sizeof(dis));
bfs(root);
maxdis=0;
for(int i=1; i<=n; i++) if(dis[i]>maxdis) maxdis=dis[i], root=i;
}

void dfs(int x, int fa, int dis)
{
ecc=max(ecc, dis);
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa || vis[to]) continue;
dfs(to, x, edge[i].dis+dis);
}
}

void solve()
{
for(int x=root; x; x=pre[x])
{
int y=0, len=0; ecc=0;
memset(vis, 0, sizeof(vis));
for(int i=x; i; i=pre[i])
{
for(int j=head[i]; j; j=edge[j].next)
if(edge[j].to==pre[i]) len+=edge[j].dis;
y=i; vis[i]=true;
if(len>s) break;
}
for(int i=x; ; i=pre[i])
{
dfs(i, 0, 0);
if(i==y) break;
}
ans=min(ans, ecc);
}
}

int main()
{
scanf("%d%d", &n, &s);
for(int i=1; i<n; i++)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
getline();
solve();
printf("%d\n", ans);
}

$O(n)$做法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#define LL long long
#define MAXN 500005

LL min(LL x, LL y) {if(x<y) return x; return y;}
LL max(LL x, LL y) {if(x<y) return y; return x;}

int n, s, lpnt, rpnt, cnt, head[MAXN], pre[MAXN];
LL ans=1e18, dis[MAXN];
bool vis[MAXN];

struct Edge {int next, to; LL dis;} edge[MAXN*2];

void addedge(int from, int to, LL dis)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
edge[cnt].dis=dis;
head[from]=cnt;
}

void dfs(int x, int fa)
{
pre[x]=fa;
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa || vis[to]) continue;
dis[to]=dis[x]+edge[i].dis;
dfs(to, x);
}
}

void getline()
{
LL maxdis=0; dis[1]=0;
dfs(1, 0);
for(int i=1; i<=n; i++) if(dis[i]>maxdis) maxdis=dis[i], lpnt=i;
maxdis=0; dis[lpnt]=0;
dfs(lpnt, 0);
for(int i=1; i<=n; i++) if(dis[i]>maxdis) maxdis=dis[i], rpnt=i;
}

void solve()
{
int l=rpnt, r=rpnt;
for(; l; l=pre[l])
{
while(pre[r] && dis[l]-dis[pre[r]]<=s) r=pre[r];
ans=min(ans, max(dis[rpnt]-dis[l], dis[r]));
}
for(int i=rpnt; i; i=pre[i]) vis[i]=true;
for(int i=rpnt; i; i=pre[i]) dis[i]=0, dfs(i, pre[i]);
for(int i=1; i<=n; i++) ans=max(ans, dis[i]);
}

int main()
{
scanf("%d%d", &n, &s);
for(int i=1; i<n; i++)
{
int x, y; LL z;
scanf("%d%d%lld", &x, &y, &z);
addedge(x, y, z);
addedge(y, x, z);
}
getline();
solve();
printf("%lld\n", ans);
}

问题描述:

给你一张有n个节点的图,其中有(n-1)条“主要边”构成一棵树,和m条“附加边”。你需要把这张图分为两个不连通的部分,你可以切除一条“主要边”和一条“附加边”,问你有多少种方案。

输入:

第一行两个整数n, m $(1<=m, n<=100000)$ 接下来n-1行表示n-1条主要边 接下来m行表示附加边

输出:

输出方案总数

思路:

这道题目的思路比较巧妙。首先考虑当我们切除一条主要边时,把树分为了两部分,但这两部分可能有附加边让它们保持联通。显然,如果没有附加边让它们保持联通,那么就有m种方案(随便切一条就OK了)。如果有一条附加边让它们联通,那么就只有1种方案,如果有大于一条附加边时,就没有符合要求的方案。

我们可以记录每一条附加边两端之间的树上路径,那么路径上的每一条边都有一条附加边让它们联通。这时我们就可以利用树上差分+LCA统计出每一条主要边有多少条附加边让它们保持联通,最后算出答案。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 1000005
using namespace std;

int n, m, ans, cnt, id, head[MAXN], num[MAXN], tag[MAXN], dfn[MAXN], ver[MAXN], dep[MAXN], logn[MAXN], f[MAXN][30];

struct Edge {int next, to;} edge[MAXN*2];

void addedge(int from, int to)
{
edge[++cnt].next=head[from];
edge[cnt].to=to;
head[from]=cnt;
}

void solve(int x, int fa)
{
num[x]+=tag[x];
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
solve(to, x);
num[x]+=num[to];
}
if(x==1) return;
if(num[x]==0) ans+=m;
if(num[x]==1) ans++;
}

void dfs(int x, int fa, int deep)
{
dfn[x]=++id; ver[id]=x; dep[id]=deep;
for(int i=head[x]; i; i=edge[i].next)
{
int to=edge[i].to;
if(to==fa) continue;
dfs(to, x, deep+1);
ver[++id]=x;
dep[id]=deep;
}
}

void init()
{
logn[1]=0;
for(int i=1; i<=n*2-1; i++) f[i][0]=i;
for(int i=2; i<=n*2-1; i++) logn[i]=logn[i/2]+1;
for(int i=1; (1<<i)<=n; i++)
{
for(int j=1; j+(1<<i)-1<=n*2-1; j++)
{
int x=f[j][i-1], y=f[j+(1<<(i-1))][i-1];
if(dep[x]<dep[y]) f[j][i]=x;
else f[j][i]=y;
}
}
}

int rmq(int l, int r)
{
int k=logn[r-l+1];
int x=f[l][k], y=f[r-(1<<k)+1][k];
if(dep[x]<dep[y]) return ver[x];
else return ver[y];
}

int lca(int x, int y)
{
int l=dfn[x], r=dfn[y];
if(l>r) swap(l, r);
return rmq(l, r);
}

int main()
{
int x, y;
scanf("%d%d", &n, &m);
for(int i=1; i<n; i++)
{
scanf("%d%d", &x, &y);
addedge(x, y);
addedge(y, x);
}
dfs(1, 0, 0);
init();
for(int i=1; i<=m; i++)
{
scanf("%d%d", &x, &y);
tag[x]++; tag[y]++;
tag[lca(x, y)]-=2;
}
solve(1, 0);
printf("%d\n", ans);
}

6月23日 AtCoder Regular Contest 99 题目链接:https://arc099.contest.atcoder.jp/

C - Minimization

思路: 最后序列的所有元素一定是序列中的最小值,然后每一次操作都能让序列中k-1个元素变为最小值,直接扫一遍就好了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <cmath>
#include <cstdio>
#include <algorithm>
#define LL long long
using namespace std;

int n, k;
LL ans;

int main()
{
scanf("%d%d", &n, &k);
for(int i=1; i<n; i+=k) ans++, i--;
printf("%lld\n", ans);
}

D - Snuke Numbers

思路: 比较神奇的一题!我们设一个增量为d,即下一个数比这个数大d。d开始是为1,后面d要么保持不变,要么乘上10,这样我们就对每一个数都计算d需不需要乘10就可以了。(其实不是很明白为什么,有一点迷)

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <cstdio>
#include <iostream>
#define LL long long
using namespace std;

LL k, n, d=1;

int s(LL x)
{
int sum=0;
while(x) sum+=x%10, x/=10;
return sum;
}

int main()
{
scanf("%lld", &k);
for(int i=1; i<=k; i++)
{
if((n+d*10)*s(n+d)<(n+d)*s(n+d*10)) d*=10;
n+=d;
printf("%lld\n", n);
}
}

E - Independence

思路: 特别好的一题。题目简化来说就是要你找两个完全子图,并且使子图中的边数最少。

考虑这张图的补图,如果补图是二分图,则存在着两个完全子图,否则输出-1。(这个画一下应该不难发现)我们对补图进行二分图染色,但要注意补图可能不连通,所以有多种染色方法。这时染成同一种颜色的节点在原图中是构成了完全图的,于是我们记录所有可能的染色方法,对于所有方法计算答案并去其中的最小值就OK了。

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include <bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;

int n, m, a, b, s1, s2, ans=INF, temp[1500], col[705], f[705];
bool mmp[705][705], vis[1005];

void dfs(int x, int color)
{
if(vis[x])
{
if(col[x]!=color) {printf("-1\n"); exit(0);}
return;
}
vis[x]=1; col[x]=color;
if(color==1) s1++;
else s2++;
for(int i=1; i<=n; i++)
{
if(mmp[x][i]==0) continue;
dfs(i, 3-color);
}
}

int main()
{
scanf("%d%d", &n, &m);
for(int i=1; i<=m; i++)
{
scanf("%d%d", &a, &b);
mmp[a][b]=mmp[b][a]=1;
}
for(int i=1; i<=n; i++)
{
for(int j=1; j<=n; j++)
{
if(i==j) mmp[i][j]=0;
else mmp[i][j]^=1;
}
}
f[0]=1;
for(int i=1; i<=n; i++)
{
if(vis[i]) continue;
s1=s2=0;
dfs(i, 2);
for(int i=0; i<=n; i++)
{
temp[i+s1]|=f[i];
temp[i+s2]|=f[i];
}
for(int i=0; i<=n; i++)
{
f[i]=temp[i];
temp[i]=0;
}
}
for(int i=0; i<=n; i++)
if(f[i]) ans=min(ans, i*(i-1)/2+(n-i)*(n-i-1)/2);
printf("%d\n", ans);
}