A.Doremy's Paint
贪心一下就知道,如果每个数都不一样的话l和r怎么选答案都是一样的,那么直接选最大范围尽可能选中更多重复的数字即可
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];
void solve(){
int n;
cin>>n;
for(int i =1;i<=n;i++)
cin>>a[i];
cout<<1<<" "<<n<<endl;
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t = 1;
cin>>t;
while(t--){
solve();
}
}
B. Doremy's Perfect Math Class
找到数列的公共gcd = g,答案就是最大的值/g。
粗略证明的话就是假设有两个数a和b(a>b),其gcd = g,那么a可以表示成 c1g b可以表示成 c2g ,无论怎么减都是g的倍数。同时c1,c2,...cn的gcd为1,那么自然会减出来1-c最大值。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];
int GCD(int a, int b) {
return b ? GCD(b, a % b) : a;
}
void solve(){
int n;
cin>>n;
for(int i =1;i<=n;i++)
cin>>a[i];
int now = a[1];
int mx = 0;
for(int i = 2;i<=n;i++){
now = GCD(now,a[i]);
mx = max(mx,a[i]);
}
cout<<mx/now<<endl;
}
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t = 1;
cin>>t;
while(t--){
solve();
}
}
C. Doremy's City Construction
由题意简单可知,一个点能连的边一共只有三种可能
- 全连比自己大的边
- 全连比自己小的边
- 只连一条边,这条边的权值和自己相等
同时确定一个点连边后又能确定其他点的性质
假设两个点a和b,a连b是因为b是大于a的边,连上以后b就绑定了性质2
那么对所有点值进行排序后,如果考虑最优的话一定是只用性质1和性质2,那么就相当于把点分为两部分,然后每个点会连另一部分的所有点。性质3特判一下。
然后我们会尽量把点均分,因为假设左边部分点的个数是a1,右边部分点的个数是a2, a1+a2=n ,边的个数 = a1∗a2 ,当a1a2尽量相等的时候积最大,然后会有例如1 2 4 4 4 4这样的数列情况,如果还是均分就会出现4和4相连的性质3情况,这时枚举把4划分到右边的情况和划分到左边的情况,取最大值即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const int maxn = 2e6+10;
int a[maxn];
map<int,int>num,pre;
int GCD(int a, int b) {
return b ? GCD(b, a % b) : a;
}
void solve(){
ll n;
cin>>n;
for(int i =1;i<=n;i++)
cin>>a[i];
int ok = 0;
for(int i =1;i<=n;i++){
if(a[i]!=a[1]) {
ok = 1;
break;
}
}
if(!ok){
cout<<n/2<<endl;
return;
}
sort(a+1,a+n+1);
ll mid = n/2;
ll i = mid,j = mid;
while(i<=n&&a[i]==a[mid])
i++;
i--;
while(j>=1&&a[j]==a[mid])
j--;
j++;
ll ans = max(1LL*(n-i)*i,1LL*(j-1)*(n-j+1));
cout<<ans<<endl;
}
//对同一个点而言,要么只能都连比其小的,要么只能都连比其大的,相等只能连一个,简单无向图不能自环不能重边
int main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int t = 1;
cin>>t;
while(t--){
solve();
}
}
D. Doremy's Pegging Game
首先我们去考虑临界情况,即去掉一个点就无法继续取下去了
如图所示,如果我们考虑到每一种这样的临界情况,我们对于其他已经去掉的点进行全排列即可获得。
那么我们想要怎么获得这样的临界情况,我们不难发现,假设是图2的正6边形,我们还有这样的情况也是临界情况
也就是说 临界情况我们可以看成是一条较长的段和一个点组成(段中的点不一定是连续的),如图4所示
同时对于段而言,不是每个不在段上的点都能构造出临界情况,而是一定要在关于中心对称的段的范围中的点(有点抽象,感觉需要抽象理解一下,证明不太会qaq)如图5所示,橙色点是中心钉子,绿色弧段是对称段,绿色点不在绿色弧段上,不能被选取。
图5
那么我们枚举这条段的长度i,然后枚举在长度i下的这个段的取点的方案数x,再乘以对于段而言,能构建临界状态点的方案数y,然后再乘以其他点的全排列即可。最后我们要在前面乘以一个n,因为我们枚举的是段长度,再枚举每个点作为起点。
其他点的全排列是因为肯定是在进入临界状态前被选取的,同时也不会因为随便取进入另一个临界状态,所以可以任意排列。
i−n%2 是对称弧段点的个数,对于奇数点的时候会少一个
同时我们代入这个式子后发现,对于偶数情况存在只需要两个点的临界情况,即弧段长度为1的情况。但这种情况我们只要确定一个点就可以确定另一个点,其他点全排列即可。对于答案的贡献是
n∗P(n−2)
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn =2e6+10;
ll n,mid;
ll mod;
ll fac[maxn], fnv[maxn];
ll qpow(ll a, ll p) {
ll res = 1;
while (p) {
if (p & 1) {
res = res * a % mod;
}
a = a * a % mod;
p >>= 1;
}
return res;
}
void init(int n) {
fnv[0] = fnv[1] = fac[0] = 1;
for (int i = 1; i <= n; i++) {
fac[i] = fac[i - 1] * i % mod;
}
fnv[n] = qpow(fac[n], mod - 2);
for (int i = n; i >= 1; i--) {
fnv[i - 1] = fnv[i] * i % mod;
}
}
ll C(int n, int m) {
if (n < m || m < 0) return 0;
return fac[n] * fnv[n - m] % mod * fnv[m] % mod;
}
ll P(int n, int m) {
if (n < m || m < 0) return 0;
return fac[n] * fnv[n - m] % mod;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
cin >> n >> mod;
init(n + 5);
ll ans = 0;
if (n % 2 == 0) {
ans = (ans + fac[n - 2]) % mod;
}
for (int i = 2; i <= (n + 1) / 2; i++) { //枚举区间长度
for (int j = 2; j <= i; j++) { //枚举区间含点
ll x = C(i - 2, j - 2); //根据i和j确定的选中的点的方案数
ll y = i-n%2; //在另一侧的维持临界情况的点的方案数
ans = (ans + x * y % mod * fac[n - j - 1] % mod) % mod; //剩下的数的全排列
//cout<<i<<" "<<j<<" "<<ans<<endl;
}
}
ans = ans * n % mod; //枚举起点
cout << ans << '\n';
return 0;
}
毛金爹代码