Codeforces Round #812 (Div. 2)
作者:互联网
Codeforces Round #812 (Div. 2)
D. Tournament Countdown
分析
头晕脑胀的,时间复杂度算错了。
我们只要发现,四个中询问两个就可以确定哪两个一定不是冠军
我们,对于四个返回的情况一个个讨论。为方便讨论,我们将四个询问的位置定位\(x_0,x_1,x_2,x_3\),并且假设询问的是\(x_0,x_3\)
1
则代表,\(x_0>x_3\)。
则,我们首先可以推断出来,\(x_3\)一定不会是赢家,\(x_0\)可能会是冠军。
其次,我们去考虑\(x_1,x_2\),因\(x_0\)可能会是赢家,因此\(x_1\)一定不会是冠军。
因此,我们可以一次去掉两个。
2
与1的情况同理,不多解释。
0
平的情况最好说了,\(x_0,x+3\)平局,则两者都不可能是冠军。
则我们就解决了这个问题。
AC_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e5 + 10,M = N*2;
int get(int a,int b)
{
cout<<"? "<<a<<" "<<b<<endl;
int x;cin>>x;
return x;
}
void solve() {
int n; cin >> n; n = 1 << n;
vector<int> a(n);
iota(a.begin(), a.end(), 1);
int m = n;
while(m >= 4) {
m /= 2;
vector<int> b(m);
int l = 0, r = m - 1;
for(int i = 0, j = m * 2 - 1; i < j; i += 2, j -= 2)
{
int res = get(a[i], a[j]);
if(res == 1) {
b[l ++ ] = a[i];
b[r -- ] = a[j - 1];
} else if(res == 0) {
b[l ++ ] = a[i + 1];
b[r -- ] = a[j - 1];
} else if(res == 2) {
b[l ++ ] = a[i + 1];
b[r -- ] = a[j];
}
}
a.swap(b);
}
int res = get(a[0], a[1]);
if(res == 1) {
cout << "! " << a[0] << endl;
} else {
cout << "! " << a[1] << endl;
}
}
int main()
{
// ios;
int T=1;
cin>>T;
while(T -- ) {
solve();
}
return 0;
}
E. Cross Swapping
分析
首先,我们需要发现一个小规律。
对于,\(a_{ij}\)其所能换过去的地方只有\(a_{ji}\),其能交换的方法是将k
选为i
或j
。
因为,我们想让字典序尽量小。因此我们一定是从左到右考虑,尽量让两个可以互相交换的位置,字典序小的地方值尽量小。
因此,我们现在来考虑,对于可以交换的两个位置的三种情况。
我们假设,t1=a[i][j],t2=a[j][i],(i<j)
t1<t2
我们对于当前的两个位置,一定是尽量不交换,或者交换两次,即我们要不交换,要不就是同时选k=i
和k=j
才是最优的。
t1==t2
交不交换都ok啦。
t1>t2
我们对于当前的两个位置,一定是交换一次,即我们一定选择k=i
或k=j
才是最优的。
如果只是简单换两个位置就简单了,但是,我们发现当我们想选择交换某两个位置时,其还会影响到其余的格子,这样就不保证是最小的字典序了。
比如,如果我们交换了a[2][3]
与a[3][2]
,我们选择的是k=2
的换法,但是此时,a[2][1]
与a[1][2]
也会发生交换,但是a[2][1]>a[1][2]
,并且其更靠前,因此不换才是最优的。
总的来说,我们字典序靠前的位置的选择情况,是会对后面选择产生影响的。
同时,我们观察到,其中的某些选择是,选择一个不选另一个
,这不难想到用扩展域并查集。
扩展域并查集
我们用i+n
来表示不选i
,i
来表示选择i
。
则我们重新回看对于交换中的三种情况,
我们假设,t1=a[i][j],t2=a[j][i],(i<j)
-
t1<t2
则,我们可以将
i
,j
放到一个集合,将i+n
与j+n
放到一个集合,这样表示要不不选,要选两个操作要一起进行。 -
t1==t2
则,我们不用进行任何操作。
-
t1>t2
则,我们可以将
i
与j+n
放到同一个集合,将j
与i+n
放到同一个集合,这样表示两者不能同时选。
最后找到操作序列时,我们一次考虑k=1,2...n
,因为我们想要字典序尽量小。
为什么用1
?因为我们想要字典序最小,所有如果第一个操作是有意义的,那我们一定会从1
开始。
这里有个小技巧,我们按顺序将所有可以做的操作集合全部合并到1
所带领的操作集合,这样最后看k
可以取的值,就是看是不是在1
的操作集合中。
那,我们来看看代码。
AC_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;
const int N = 1e5 + 10,M = N*2;
int p[N<<1];
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]) ;
return p[x];
}
void merge(int a,int b)
{
int pa = find(a),pb = find(b);
if(pa!=pb) p[pa] = pb;
}
void solve()
{
int n;cin>>n;
vector<vector<int>> a(n+1,vector<int>(n+1));
for(int i=1;i<=2*n;i++) p[i] = i;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(i!=j)
{
int t1 = a[i][j],t2 = a[j][i];
if(t1==t2) continue;
if(t1<t2)
{
if(find(i)!=find(j+n))
{
merge(i,j);
merge(i+n,j+n);
}
}
if(t2<t1)
{
if(find(i)!=find(j))
{
merge(i,j+n);
merge(i+n,j);
}
}
}
for(int i=1;i<=n;i++)
if(find(i)!=find(1)&&find(i+n)!=find(1))
{
merge(1,i);
merge(1+n,i+n);
}
for(int i=1;i<=n;i++)
if(find(i)==find(1))
for(int j=1;j<=n;j++)
swap(a[i][j],a[j][i]);
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
cout<<a[i][j]<<" ";
cout<<"\n";
}
}
int main()
{
ios;
int T=1;
cin>>T;
while(T -- ) {
solve();
}
return 0;
}
标签:Div,int,t2,交换,Codeforces,t1,812,我们,define 来源: https://www.cnblogs.com/aitejiu/p/16558811.html