Marin and Anti-coprime Permutation(800)
思路(规律的做法)
- 场上很多人过,而自己完全没思路
- 划分解空间:考虑最大公约数可以取哪些值
- 好像这种做不出来的可行的解通常个数很少??
- 可以多写几个排列发现规律。最终发现最大公约数只可能小于等于2.难度一下降低了!
- 当最大公约数等于2时,只有n为偶数的时候可以满足,并且奇数与偶数配对,偶数与奇数配对。每个 n ! 2 \frac{n!}{2} 2n!种。
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int n; cin >> n;
if(n%2) cout << 0 << endl;
else
{
ll a = 1;
for(long long i = 1;i<=n/2;i++) a = (a*i)%mod;
a = (a % mod) * (a % mod) % mod;
cout << a << endl;
}
}
system("pause");
}
证明 g = g c d ( 1 ⋅ p 1 , 2 ⋅ p 2 ⋯ n ⋅ p n ) ≤ 2 g = gcd(1 \cdot p_1,2\cdot p_2 \cdots n \cdot p_n) \leq 2 g=gcd(1⋅p1,2⋅p2⋯n⋅pn)≤2(GOOD)
- 有了上述的猜想,说不定可以顺水推舟完成证明。
- 如果 g = 2 k , k > 1 g = 2^k,k>1 g=2k,k>1,那么 1 ⋅ p 1 , 2 ⋅ p 2 ⋯ n ⋅ p n 1 \cdot p_1,2\cdot p_2 \cdots n \cdot p_n 1⋅p1,2⋅p2⋯n⋅pn都是偶数,只能按照上述思路中的配对方式进行配对。这时 p 2 p_2 p2是奇数,不含有2的因子,因此 2 ⋅ p 2 2 \cdot p_2 2⋅p2不是2的幂次(幂次大于1)的形式
- 否则,g不是 2 k 2^k 2k,且k>1的形式。则存在质数 p > 2 p>2 p>2使得g含有p的因子,即 p ∣ g p|g p∣g。在1-n的排列中仅有 ⌊ n p ⌋ \lfloor \frac{n}{p} \rfloor ⌊pn⌋能被p整除。在这些满足条件的数中,由于p是质数,仅仅自身与某个不同的这样的数配对而产生的乘积能被p整除,因此只有 2 ⌊ n p ⌋ 2\lfloor \frac{n}{p} \rfloor 2⌊pn⌋个配对。由于 p ≥ 3 p \geq 3 p≥3,因此配对最多有 2 ⌊ n 3 ⌋ < n 2\lfloor \frac{n}{3} \rfloor<n 2⌊3n⌋<n个,即不存在配对方式使得所有配对(n个配对)的乘积含有因子p,自然不含有因子p;
Shinju and the Lost Permutation(1700-必要条件也是充分条件)
思路
- 寻找特殊值:最大值排在第一个对应的power必为1,其余的都>1;等价地可以从c为1开始的值开始构建排列
- 考察两个邻接的c。必要条件 c i + 1 − c i ≤ 1 c_{i+1} - c_i \leq 1 ci+1−ci≤1,也是充分的,可以使用拓扑排序证明。(只需要证明拓扑排序存在即可 即不会形成环路)
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int n;
cin >> n;
int c[n];
int index1 = -1;
int count1 = 0;
for(int j = 0;j<n;j++)
{
cin>>c[j];
if(c[j] == 1) {count1++;index1 = j;}
}
if(count1>1||count1<=0) cout<<"NO"<<endl;
else
{
count1=0;
int j = index1;
int last = c[j];
j=(j+1)%n;
while(count1<n-1)
{
if(c[j]-last<=1)
{
last = c[j];
j=(j+1)%n;
count1++;
}
else
{
cout<<"NO"<<endl;
break;
}
}
if(count1 == n-1)cout<<"YES"<<endl;
}
}
system("pause");
}
D题
388535 (Easy Version-1600)(必要条件也是充分条件)
思路
- 未想到的点:将二进制比特稍微列一下然后可以顺水推舟解决问题。思维的断点:注意经过异或后a数组的特殊性,自然需要考虑下原排列。
- 异或运算的结合律 ( a ⊕ x ) ⊕ x = a (a \oplus x) \oplus x = a (a⊕x)⊕x=a,异或运算如果与0异或起到保留作用,与1异或起到取反作用
- 对于处理后的a数组每个元素每一个二进制位,考察1的个数的总和(必要条件),来构造x:
- 如果该位1的个数总和不等于原始a(未经过异或)的1的个数总和,说明该位与x异或后一定进行了翻转,那么只有将x的该位设为1才能保证异或后的a数组每个元素与构造出的x异或后与原始a数组对应位的1的个数总和相同。
- 否则,该位中0的个数总和也等于原始a中该位的0的个数总和。
- 如果1的个数不等于0的个数,x的该位必须置0,分析同理
- 如果1的个数等于0的个数,则x从该位往后的位都可以任意取0或1.这是因为a此时元素的个数一定为2的次幂形式,从该位往后原始a中元素中每一位0的个数和1的个数均相同,当然对异或后的a也一样。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
using namespace std;
const int mod = 998244353;
typedef long long ll;
int main()
{
int t;
cin >> t;
for(int i = 0;i<t;i++)
{
int l,r;
cin >> l >> r;
int a_r[19]; memset(a_r,0,19*sizeof(int));
int a_c[19];memset(a_c,0,19*sizeof(int));
for(int k = 1;k<=r-l+1;k++)
{
int a; cin >> a;
int j = 1;
while(a>0)
{
a_r[j]+=a&1; //最低位
a=a>>1;
j++;
}
int o =k-1;
j =1;
while(o>0)
{
a_c[j]+=o&1;
o=o>>1;
j++;
}
}
//统计完成后构造x
int x= 0;
for(int j = 1;j<19;j++)
{
if(a_c[j]!=a_r[j]) x+=(1 << (j-1));
}
cout << x << endl;
}
system("pause");
}
388535 (HARD Version)(必要条件也是充分条件)
- 较为复杂的解法:只需要考虑easy version中0和1相等的情况该取0还是取1;
My Solution
- 当诸位确定x的取值时,记录取到的每个由a变换过来的前缀prefix。当遇到0和1相等的时候两种情况:
- 如果prefix全部相等,需要分别令当前位取1,取0,分两支搜索,且分支搜索后两个都不会完全相同。看最终结果哪个满足条件
- 否则prefix不等,继续循环即可。
- 下述代码有很多重复冗余,但还没想好咋优化。。。。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
using namespace std;
const int mod = 998244353;
typedef long long ll;
int main()
{
int t;cin>>t;
for(int i = 0;i<t;i++)
{
int l,r; cin >> l >> r;
int a[r-l+1];
memset(a,0,(r-l+1)*sizeof(int));
int a_e[18];
int a_a[18]; //1到17位放置
memset(a_e,0,18*sizeof(int));
memset(a_a,0,18*sizeof(int));
for(int j = l;j<=r;j++)
{
int c; cin >> c;
a[j-l]=c;
int k = 16; //从最高位开始放置
while(k>=0)
{
a_e[17-k]+=((j>>k)&1); //统计排列从低位到高位的1的个数情况
a_a[17-k]+=((c>>k)&1); //统计现有数据从最高位到最低位1的个数
k--;
}
}
int x = 0,j=1; //构造x,从最高位开始逐层往后构造
int prefix[r-l+1];
memset(prefix,0,(r-l+1)*sizeof(int));
bool flag = false;
while(j<=17)
{
if(a_e[j]!=a_a[j])
{
x+=(1<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(1^((a[k] >> (17-j))&1));
}
else //a_e[j]==a_a[j]
{
if((r-l+1)-a_e[j]!=a_e[j])
{
x+=(0<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(0^((a[k] >> (17-j))&1));
}
else //1的个数和0的个数相同
{
bool allequal = true; //是否所有都相同
int maxi = prefix[0];
for(int k = 1;k<r-l+1;k++)
{
if(prefix[k]!=prefix[k-1]) allequal = false;
maxi = max(maxi,prefix[k]);
}
if(!allequal)
{
//找出所有的最大值,如果1的个数和0的个数不相同
//则需要让0的个数比1的个数多
int count1 = 0,count0 = 0;
for(int k = 0;k<r-l+1;k++)
{
if(prefix[k]==maxi) //找出所有最大值的k
{
count1+=(a[k]>>(17-j))&1;
count0+=(!((a[k]>>(17-j))&1));
}
}
int add = 0;
if(count0>count1) add=0; else add=1;
//更新prefix前缀
x+=(add<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix[k]=(prefix[k]<<1)+(add^((a[k] >> (17-j))&1));
}
else {flag = false;break;}
}
}
j++;
}
if(!flag)
{
int x_1 = x+(1<<(17-j));
int prefix1[r-l+1];
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix[k]<<1)+(1^((a[k] >> (17-j))&1));
int j_1 = j;
j++;
while(j<=17)
{
if(a_e[j]!=a_a[j])
{
x_1+=(1<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(1^((a[k] >> (17-j))&1));
}
else
{
if((r-l+1)-a_e[j]!=a_e[j])
{
x_1+=(0<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(0^((a[k] >> (17-j))&1));
}
else
{
int maxi = prefix1[0];
for(int k = 1;k<r-l+1;k++)
{
maxi = max(maxi,prefix1[k]);
}
int count1 = 0,count0 = 0;
for(int k = 0;k<r-l+1;k++)
{
if(prefix1[k]==maxi) //找出所有最大值的k
{
count1+=(a[k]>>(17-j))&1;
count0+=(!((a[k]>>(17-j))&1));
}
}
int add = 0;
if(count0>count1) add=0; else add=1;
//更新prefix前缀
x_1+=(add<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(add^((a[k] >> (17-j))&1));
}
j++;
}
}
int x_2 = x+(0<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix[k]<<1)+(0^((a[k] >> (17-j))&1));
j=++j_1;
while(j<=17)
{
if(a_e[j]!=a_a[j])
{
x_2+=(1<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(1^((a[k] >> (17-j))&1));
}
else
{
if((r-l+1)-a_e[j]!=a_e[j])
{
x_2+=(0<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(0^((a[k] >> (17-j))&1));
}
else
{
int maxi = prefix1[0];
for(int k = 1;k<r-l+1;k++)
{
maxi = max(maxi,prefix1[k]);
}
int count1 = 0,count0 = 0;
for(int k = 0;k<r-l+1;k++)
{
if(prefix1[k]==maxi) //找出所有最大值的k
{
count1+=(a[k]>>(17-j))&1;
count0+=(!((a[k]>>(17-j))&1));
}
}
int add = 0;
if(count0>count1) add=0; else add=1;
//更新prefix前缀
x_2+=(add<<(17-j));
for(int k = 0;k<r-l+1;k++) prefix1[k]=(prefix1[k]<<1)+(add^((a[k] >> (17-j))&1));
}
j++;
}
}
bool flag = true;
set<int> g;
for(int i =0;i<r-l+1;i++)
{
if(g.find(a[i]^x_1)==g.end()&&(a[i]^x_1)>=l&&(a[i]^x_1)<=r) g.insert(a[i]^x_1);
else{
flag=false;
break;
}
}
if(flag) cout << x_1<<endl;
else cout<<x_2<<endl;
}
else cout << x << endl;
}
system("pause");
}
官方解法(待更新)
- 可能的x的空间: a i ⊕ l , ∀ i a_i \oplus l,\forall i ai⊕l,∀i
- 遍历空间中每个x。
- 由 a i a_i ai的构造, a i a_i ai是互不相同的。故对每个候选x, a i ⊕ x a_i\oplus x ai⊕x也是互不相同的。
- 因此只要 m i n i { a i ⊕ x } = l , m a x i { a i ⊕ x } = r min_i \{a_i\oplus x\} =l,max_i \{a_i\oplus x\} =r mini{ai⊕x}=l,maxi{ai⊕x}=r即可。这是字典树的应用(必要条件也是充分条件)
- 结点最多个数:最底层r-l+1个。以上每一层<=r-l+1个结点。共32层。
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
#include<math.h>
typedef long long ll;
using namespace std;
ll idx=0;
void insert(ll x,vector<vector<ll>> &nxt)
{
ll now = 0 ;
for(int i = 31;i>=0;i--)
{
int y = (x>>i)&1;
if(!nxt[now][y]) nxt[now][y]=++idx; now=nxt[now][y];
}
}
ll query_max(ll x,vector<vector<ll>> &nxt)
{
ll now = 0,res=0;
for(int i = 31;i>=0;i--)
{
int y = (x>>i)&1;
if(nxt[now][y^1])
{
res+=(1<<i);
now=nxt[now][y^1];
}
else now = nxt[now][y];
}
return res;
}
ll query_min(ll x,vector<vector<ll>> &nxt)
{
ll now = 0,res = 0;
for(int i = 31;i>=0;i--) //到最后一层结点一定存在
{
int y = (x>>i)&1;
if(nxt[now][y]) //相同为0
{
now = nxt[now][y];
}
else
{
now = nxt[now][y^1];
res += (1 << i);
}
}
return res;
}
int main()
{
int t;
cin>>t;
for(int i = 0;i<t;i++)
{
ll l,r;
cin>>l>>r;
vector<ll> a;
vector<vector<ll> > nxt((r-l+1)*32 , vector<ll>(2 , 0)) ;
idx = 0;
for(int k = 0;k<r-l+1;k++)
{
ll c;cin>>c;a.push_back(c);
insert(c,nxt);
}
//枚举每一个x
ll x;
for(ll d : a)
{
x = d^l;
if(query_max(x,nxt)==r&&query_min(x,nxt)==l) break;
}
cout << x << endl;
}
system("pause");
}
E题:Gojou and Matrix Game(2500-GOOD)
- 此题思维量较大。是很好的一道博弈题,还反映出对绝对值不等式的理解不够深入
- 另:使用cin超时,换成scanf
思路
-
设当前点坐标(i,j)。假设窗口外的点的输赢情况已确定。如果:
- 最后一步在窗口外下的点如果全为输,则该点最后下必赢,否则必输。
-
看似是一个先有鸡还是先有蛋的问题。实际上可以采用一定的顺序合理解决。
-
按权值从大到小的顺序填写。令 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1当且仅当最后一步在(i,j)下会赢。权值最大点处显然有 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1.考察权值第二大的点,如果权值最大的点在其窗口内,那么对手只能采取在比这个权值第二大点小的点落子。以此类推。
-
即 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1当且仅当 ∀ i , , j , ∈ S , ∣ i − i , ∣ + ∣ j − j , ∣ ≤ k \forall i^,,j^,\in S,|i-i^,|+|j-j^,|\leq k ∀i,,j,∈S,∣i−i,∣+∣j−j,∣≤k,其中S为按上述顺序已经确定的 d p [ i ] [ j ] = 1 dp[i][j]=1 dp[i][j]=1的点集合。
-
判断 ∀ i , , j , ∈ S , ∣ i − i , ∣ + ∣ j − j , ∣ ≤ k \forall i^,,j^,\in S,|i-i^,|+|j-j^,|\leq k ∀i,,j,∈S,∣i−i,∣+∣j−j,∣≤k是否成立,由绝对值不等式,等价于 ∣ i − i , + j − j , ∣ ≤ k |i-i^, + j-j^,| \leq k ∣i−i,+j−j,∣≤k和 ∣ i − i , − j + j , ∣ ≤ k |i-i^, - j+j^,| \leq k ∣i−i,−j+j,∣≤k是否同时成立。(或分同号和异号讨论一下)只需存储8个数值:
- j − i j-i j−i的最大值及其对应的 j + i j+i j+i
- j − i j-i j−i的最小值及其对应的 j + i j+i j+i
- j + i j+i j+i的最大值及其对应的 j − i j-i j−i
- j + i j+i j+i的最小值及其对应的 j − i j-i j−i
-
事实上,只需存储4个值。对应的所有数值可以全部的全去掉。
代码
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
using namespace std;
const int mod = 998244353;
typedef long long ll;
int v[2005][2005]; //保存结果
pair<int,int> a[4000003]; //值到位置的映射
int main()
{
int n,k;
scanf("%d %d",&n,&k);
for(int i = 1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
scanf("%d",&v[i][j]);
a[v[i][j]].first=i;
a[v[i][j]].second=j;
}
}
int iplusjmin = a[n*n].first+a[n*n].second;
int iplusjmax = a[n*n].first+a[n*n].second;
int iminusjmin = a[n*n].second-a[n*n].first;
int iminusjmax = a[n*n].second-a[n*n].first;
v[a[n*n].first][a[n*n].second]=1;
for(int p = n*n-1;p>=1;p--)
{
int i = a[p].first;
int j = a[p].second;
int a = i+j-iplusjmax;
int b = i+j-iplusjmin;
int c = i-j+iminusjmin;
int d = i-j+iminusjmax;
if(max(max(max(abs(a),abs(b)),abs(d)),abs(c))<=k)
{
v[i][j]=1;
iplusjmin = min(i+j,iplusjmin);
iplusjmax = max(i+j,iplusjmax);
iminusjmin = min(j-i,iminusjmin);
iminusjmax = max(j-i,iminusjmax);
}
else v[i][j]=0;
}
for(int i =1;i<=n;i++)
{
for(int j = 1;j<=n;j++)
{
if(v[i][j]) printf("M");
else printf("G");
}
printf("\n");
}
system("pause");
}
F-Juju and Binary String(2700-GOOD)重新解读式子的含义
思路
- 利用必要条件缩小搜索空间:猜测只可能拆成很少的几块,然后讨论剩下的
- 是否存在很好判断。不说了。
- 目标中1的个数
x = m ⋅ # o n e s n x = \frac{m \cdot \# ones}{n} x=nm⋅#ones
上述式子什么含义?
- 将原始字符串首尾相连,从任意点开始所有长度为m的滑动窗口中1的个数的均值!
- 注意到原始字符串中1个1在m个窗口中出现。
- 对所有滑动窗口1的个数都不能同时大于均值,或同时小于均值。同时,由于相邻滑动窗口中1的个数至多相差1.因此一定存在某个滑动窗口中1的个数取到均值x.
代码
#include<iostream>
#include<algorithm>
#include<map>
#include<stdio.h>
#include<string.h>
#include<unordered_map>
#include<vector>
#include<queue>
#include<set>
using namespace std;
const int mod = 998244353;
typedef long long ll;
int main()
{
int t;
cin >> t;
for(int l = 0;l<t;l++)
{
int n,m;
cin >> n >> m;
string s; cin >> s;
int count1=0;
for(int k = 0;k<s.size();k++) {count1+=(s[k]-'0');}
if((ll)count1*(ll)m%(ll)n!=0)cout<<-1<<endl;
else //一定存在
{
count1=(int)((ll)count1*(ll)m/(ll)n);
//从首个开始的滑动窗口中1的个数
int c_1=0;
for(int i =0;i<m;i++) c_1+=(s[i]-'0');
if(c_1==count1)
{
cout<<1<<endl;
cout<<1<<" "<<m<<endl;
}
else
{
for(int i = 1;i<n;i++)
{
c_1-=(s[i-1]-'0');
c_1+=(s[(i+m-1)%n]-'0');
if(c_1==count1)
{
if(i+m-1<n)
{
cout<<1<<endl;
cout<<i+1<<" "<<i+m<<endl;
}
else
{
cout<<2<<endl;
cout<<1<<" "<<(i+m-1)%n+1<<endl;
cout<<i+1<<" "<<n<<endl;
}
break;
}
}
}
}
}
system("pause");
}