A. 序列
题意:
已知(1)到(n)的一个排列。
现在给定一个数(k),对于这个排列的一个长度大于等于(2)的子序列(s=(s_1,...,s_p),p≥2), 对于每一个下标(i),如果满足:
- (i<p)且(s_i<k<s_{i+1});
- (i<p)且(s_i>k>s_{i+1}),那么得分加(1)。
例如,当(k=2)时,子序列5134的得分就是2。
现在询问当(k)取遍(1)到(n)时,所有给定排列的子序列的得分和是多少。
思路:
- 我们可以单独考虑排列中每两个数((i,j))对答案的贡献,显然两个数对答案的贡献为(2^{n+p_i-p_j-1},p_i<p_j)。
- 考虑枚举答案,当枚举到(k)时,我们需要考虑区间([1,k-1],[k+1,n])中互相配对产生的贡献。
- 假设已经处理完了答案为(k)的情况,现在要处理答案为(k+1)的情况,那么此时我们要处理的区间为([1,k],[k+2,n])。观察区间的变化:发现我们需要加上(k, [k+2,n])的贡献,减去([1,k-1],k+1)的贡献。
- 那么我们从前往后、从后往前扫一遍预处理出贡献即可。预处理用树状数组实现单点修改、区间询问。
还有很多其它做法,个人感觉另外一种做法十分简单,用四个树状数组来直接统计答案:假设枚举到(i),那么两个树状数组记录([i+1,n])的信息,两个树状数组记录([1,i-1])的信息即可。
总时间复杂度(O(nlogn))。
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/21 16:11:43
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5, MOD = 1e9 + 7, inv2 = (MOD + 1) / 2;
struct Bit {
int c[N];
void init() {
memset(c, 0, sizeof(c));
}
int lowbit(int x) {return x & (-x);}
void add(int x, int v) {
for(; x < N; x += lowbit(x)) {
c[x] = (c[x] + v) % MOD;
}
}
int query(int x) {
int res = 0;
for(; x; x -= lowbit(x)) {
res = (res + c[x]) % MOD;
}
return res;
}
int query(int l, int r) {
if(l > r) return 0;
return (query(r) - query(l - 1) + MOD) % MOD;
}
}bit, bit2;
int n, ans[N];
int a[N], b[N];
int l[N], r[N];
int f[N << 1], g[N << 1];
void run(){
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i], b[a[i]] = i;
for(int i = 1; i <= n; i++) {
bit.add(b[i], f[n + b[i] - 1]);
bit2.add(b[i], (n - b[i] - 1 < 0 ? inv2 : f[n - b[i] - 1]));
if(i + 2 <= n) {
int t = bit.query(1, b[i + 2] - 1), t2 = bit2.query(b[i + 2] + 1, n);
l[i + 2] = (1ll * t * g[b[i + 2]] % MOD + 1ll * t2 * f[b[i + 2]] % MOD) % MOD;
}
}
bit.init(); bit2.init();
for(int i = n; i >= 1; i--) {
bit.add(b[i], f[n + b[i] - 1]);
bit2.add(b[i], (n - b[i] - 1 < 0 ? inv2 : f[n - b[i] - 1]));
if(i - 2 >= 1) {
int t = bit.query(1, b[i - 2] - 1), t2 = bit2.query(b[i - 2] + 1, n);
r[i - 2] = (1ll * t * g[b[i - 2]] % MOD + 1ll * t2 * f[b[i - 2]] % MOD) % MOD;
}
}
for(int i = 2; i < n; i++) {
ans[i] = ((ans[i - 1] - l[i] + MOD) % MOD + r[i - 1]) % MOD;
}
for(int i = 1; i <= n; i++) {
cout << ans[i] << '
';
}
}
void init() {
f[0] = g[0] = 1;
for(int i = 1; i < N << 1; i++) {
f[i] = 1ll * f[i - 1] * 2 % MOD;
g[i] = 1ll * g[i - 1] * inv2 % MOD;
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
init();
run();
return 0;
}
C. 10^5万
题意:
现有(1...n)的每种数字的牌各(4)张。玩家选择(3k+1)张牌,然后随机获得数字为(1...n)的一张牌(即使已经有(4)张有可能再次获得)。
现在给出(n,L),要求选出长度为(3k+1)的序列((k)自己来定),使得这(3k+2)个数能够不重不漏地划分为一个对子和(k)个面子。对子即("x x"),面子即("x x x","x x+1 x+2")。
思路:
构造题,其实可以通过样例猜出来。
我们直接按照模(3)的余数分类:
- 对于模(3)余(1)或(2)的情况,我们直接(1 1 1 2 2 2cdots l-1 l-1 l-1 l-1)即可;
- 对于模(3)余(0)的情况,我们直接(2 2 2 cdots l l l l)即可。
至于正确性的证明,数据较小的情况我们可以直接验证,数据大的情况我们可以将其规约为数据较小的情况。规约方法为:最后四个拿出三个做面子,然后依次往前做面子即可,发现是以(3)为循环的,根据小情况就可以统计出方案数了。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/20 10:03:05
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int n, l;
void run(){
cin >> n >> l;
if(l == 2) {
cout << 1 << '
' << 1 << ' ' << 1 << ' ' << 2 << ' ' << 2 << '
';
return;
}
vector <int> ans;
ans.push_back(1);
ans.push_back(1);
ans.push_back(1);
if(l % 3) {}
else {
if(n == l) {
for(int i = 2; i < l; i++) ans.push_back(i);
ans.push_back(l);
ans.push_back(l);
ans.push_back(l);
cout << sz(ans) / 3 << '
';
for(int i = 0; i < sz(ans); i++) cout << ans[i] << "
"[i == sz(ans) - 1];
return;
} else {
ans.clear(); ++l;
}
}
int now = 1;
while(now + 1 < l) {
++now;
ans.push_back(now);
ans.push_back(now);
ans.push_back(now);
}
ans.push_back(now);
cout << sz(ans) / 3 << '
';
for(int i = 0; i < sz(ans); i++) cout << ans[i] << "
"[i == sz(ans) - 1];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
F. 草莓
题意:
给出一个(ncdot m,n,mleq 10^9)的格子,从第一天开始,每天早上,每个格子会多出一个草莓。
一开始起点为((x,y)),每天下午可以选在待在原地或者向周围一个格子走去。
每天晚上会自动收割当前格子内的草莓。
现在问总共有(k)天,最多能收获多少草莓。
思路:
有几个比较重要的观察:
- 考虑最后的局面,最优局面一定为格子中的权值由小到大为:(0,1,cdots,ncdot m-1)。因为产生草莓的总数量一样多,这样所剩的草莓最少。
- 达到最优局面意味着:最后会将每个格子走一遍。
- 格子中的草莓可以留着等最后一次经过时再采,这样可以方便计算。
若(ncdot m)为偶数,则必然存在一条哈密顿回路,所以我们直接等到最后再来走即可;
若(ncdot m)为奇数,此时我们能够遍历所有的格子的条件为起点((x,y))满足(x+y)为偶数。
- 证明方法分(x,y)奇偶性讨论,事实上我们只需要讨论(x,y)都为奇数的情况,这种情况最终一定会归约到一个(n'cdot m',n',m')为奇数的矩形且起点位于四个顶点之一的情况。因此可以直接走完。
- 如果(x+y)为奇数,则其中一奇一偶。最终会规约到一个(n'cdot m',n',m')为奇数且起点为((x',y'),x',y')一奇一偶的情况,显然不可能为四个顶点之一,最终不能走完。
回到这个题,如果一开始在的位置(x+y)为奇数,我们直接在第一天下午走一步即可。
以上讨论适用于(ngeq 2,mgeq 2)的情况,接下来考虑其中有一个为(1)的情况。
不妨(n=1),根据最初的观察,我们要使得(0,1,cdots)这样的权值序列最长,那么就有以下的贪心方法:
- 若(k)足够大,我们一开始走到一个角落里,然后等到最后(m)步将方格全部遍历一遍即可;
- 若(k)比较小,我们就走(k)步即可;
- 若(k)不大不小,我们选择往小的那一边走几步然后回头往另外一边走,并且保证要走到另一边的端点处。
最后(n=1)的情况实现起来要考虑一点细节,尤其是第三种情况,我们先把较长的那一段距离(l)求出来,然后(lfloorfrac{k-l}{2}
floor)即是我们需要等待的时间,从第(lfloorfrac{k-l}{2}
floor+1)天起走向另一边即可。
可能有点细节,还要注意取模,详见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/20 15:42:30
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5, MOD = 998244353, inv2 = 499122177;
ll f(ll l, ll r) {
if(l > r) return 0;
return 1ll * (l + r) % MOD * ((r - l + 1) % MOD) % MOD * inv2 % MOD;
}
void run(){
ll n, m, x, y, k;
cin >> n >> m >> x >> y >> k;
if(m == 1) swap(n, m), swap(x, y);
ll ans = 0, t = n * m;
if(m == 1) {
ans = k % MOD;
} else if(n == 1) {
ll d = max(min(y - 1, m - y) - 1, 0ll);
if(k >= d + m) ans = f(k - m + 1, k);
else if(k >= m - d) {
ll l = m - d - 1;
ans = f((k - l) / 2 + 1, k);
} else ans = f(1, k);
} else {
if(k <= t) ans = f(1, k);
else ans = f(k - t + 1, k);
}
cout << ans << '
';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
G. 草莓2
这个题就简单多了,(ncdot mleq 12)且保证至少有一个偶数,也就是怎么都存在哈密顿回路。
所以当(k>ncdot m)的时候直接走哈密顿回路,(kleq ncdot m)时直接爆搜即可。
Code
/*
* Author: heyuhhh
* Created Time: 2020/2/20 11:46:33
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 20 + 5;
int n, m, x, y;
ll k, ans;
int a[N][N];
pii T[N];
const int dx[] = {1, -1, 0, 0, 0}, dy[] = {0, 0, 1, -1, 0};
void dfs(int cur, int x, int y, int sum) {
if(cur == k + 1) {
ans = max(ans, (ll)sum);
return;
}
int t = 0;
for(int i = cur - 1; i > 0; i--) {
if(T[i] == MP(x, y)) {
t = i; break;
}
}
T[cur] = MP(x, y);
if(t == 0) sum += a[x][y] + cur - 1;
else sum += cur - t;
for(int i = 0; i < 5; i++) {
int nx = x + dx[i], ny = y + dy[i];
if(nx >= 1 && nx <= n && ny >= 1 && ny <= m) {
dfs(cur + 1, nx, ny, sum);
}
}
}
void run() {
cin >> n >> m >> x >> y >> k;
int sum = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
cin >> a[i][j];
sum += a[i][j];
}
}
if(k > n * m) {
int t = n * m;
ans = sum + (t - 1) * t / 2;
ans += 1ll * ((k - t) / t) * (t * t);
ans += t * (k % t);
} else {
dfs(1, x, y, 0);
}
cout << ans << '
';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
H. 游戏
考虑将所有删除的数字拿出来,最终总共会有(n!)总排列。
考虑每个质数对的贡献:
(n)为偶数,每一对的贡献为(frac{1}{n-1})。
(n)为奇数,每一对的贡献为(frac{1}{n})。
所以直接统计互质对数就行。
Code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int MAXN = 5e3+5,MAXM = 1e6+5,MOD = 998244353,INF = 0x3f3f3f3f,N=2e5;
const ll INFL = 0x3f3f3f3f3f3f3f3f;
const db eps = 1e-7;
#define lson o<<1,l,m
#define rson o<<1|1,m+1,r
#define mid l + ((r-l)>>1)
#define pii pair<int,int>
#define vii vector<pii>
#define vi vector<int>
#define x first
#define y second
using namespace std;
int n,primes[MAXN],phi[MAXN],cnt,sum[MAXN];
void init(){
phi[1] = 1;
for(int i=2;i<=5000;i++){
if(!phi[i]){
primes[cnt++] = i;
phi[i] = i-1;
}
for(int j=0;j<cnt&&primes[j]*i<=5000;j++){
if(i%primes[j]==0){
phi[i*primes[j]] = phi[i]*primes[j];
break;
}
phi[i*primes[j]] = phi[i] * (primes[j]-1);
}
}
for(int i=2;i<=5000;i++)sum[i] = sum[i-1] + phi[i];
}
int gcd(int a,int b){
return !b?a:gcd(b,a%b);
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
init();
cin>>n;
if(n==2){
cout<<1<<'/'<<1<<'
';
return 0;
}else if(n==1){
cout<<0<<'/'<<1<<'
';
return 0;
}
int a = sum[n];
int b = (n&1?n:n-1);
int g = gcd(a,b);
cout<<a/g<< '/'<<b/g<<'
';
return 0;
}
I. 圆
不太会。放上队友写的代码。
Code
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i=(a); i<(b); i++)
using namespace std;
#define double long double
typedef long long ll;
#define MAXN 100007
double a[MAXN];
const double PI=acos(-1);
int main() {
int n; scanf("%d", &n);
REP(i,0,n) {
scanf("%Lf", &a[i]);
a[i]*=n;
}
sort(a,a+n);
REP(i,0,n) {
a[i] = 360.0*i-a[i];
}
sort(a,a+n);
// int md=n/2;
//double kk=a[md]/360;
//int ku = (int)(ceil(kk)+0.1), kd=(int)(floor(kk)+0.1);
double ans1=0;
int l=0,r=n;
while(l<r) {
ans1+=fabs(a[r-1]-a[l]);
l++,r--;
}
/* if((n&1) == 0) {
double ans3=0, ans4=0;
double kk=a[md-1]/360;
int ku=(int)(ceil(kk)+0.1), kd=(int)(floor(kk)+0.1);
REP(i,0,n) {
ans3+=fabs(a[i]-ku*360.0);
ans4+=fabs(a[i]-kd*360.0);
}
ans1=min(ans1,min(ans3,ans4));
}*/
printf("%.10Lf
", ans1/n/180*PI);
return 0;
}
K. 修炼
二分最短天数,然后贪心计算贡献即可。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int main() {
int a1, a2; scanf("%d%d", &a1, &a2);
//a1++, a2++;
int n; scanf("%d", &n);
int ans=(-1u)>>1;
for(int i=0; i<n; i++) {
int b1, b2; scanf("%d%d", &b1, &b2);
ll l=0, r=max(b1,b2)*2;
while(l<r) {
ll m=(l+r)>>1;
ll v1=m*a1, v2=m*a2;
for(long long j=m; j>0; j--) {
if(b1-v1>b2-v2) v1+=j;
else v2+=j;
if(v1>=b1 && v2>=b2) {r=m;goto nxt;}
}
l=m+1;
nxt:;
}
if(r<ans) ans=r;
}
printf("%d
", ans);
return 0;
}
L. 图
因为点数只有(20)个,所以总共的状态数只有(2^{20})个,因此我们可以直接暴力计算所有状态,并且找到循环节。
我们把每个状态看作一个点,每个状态向另一个状态转化就相当于在状态图中连了一条有向边,那么最终图的形式一定为长度为(l,lgeq 0)的链+一个环。
求出这个之后,对于每组询问,我们只需要知道(x)点是否在环中、最终走到哪个位置就行。
可能会有一些细节,详见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/1/18 16:25:39
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#define MP make_pair
#define fi first
#define se second
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
//#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << '
'; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
#else
#define dbg(...)
#endif
void pt() {std::cout << '
'; }
template<typename T, typename...Args>
void pt(T a, Args...args) {std::cout << a << ' '; pt(args...); }
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 20 + 1;
int n, m, q;
int a[N], d[N];
int cnt[N][1 << N], tot[N], num[N], sum[N];
vector <int> rg[N];
int mark[1 << N];
int get() {
int res = 0;
for(int i = 1; i <= n; i++) if(a[i]) {
res ^= (1 << (i - 1));
}
return res;
}
void run() {
for(int i = 1; i <= n; i++) {
cin >> a[i];
if(a[i]) cnt[i][++tot[i]] = 0;
}
for(int i = 1; i <= m; i++) {
int u, v; cin >> u >> v;
rg[v].push_back(u);
d[v] ^= a[u];
}
memset(mark, -1, sizeof(mark));
mark[get()] = 0;
int len = 1 << n, s = 0;
for(int T = 1; T < (1 << n); T++) {
for(int u = 1; u <= n; u++) {
if(d[u]) a[u] = 1;
else a[u] = 0;
}
int tmp = get();
if(mark[tmp] != -1) {
s = mark[tmp];
len = T - s;
break;
}
mark[tmp] = T;
for(int u = 1; u <= n; u++) {
d[u] = 0;
for(auto v : rg[u]) {
d[u] ^= a[v];
}
}
for(int i = 1; i <= n; i++) if(a[i]) {
cnt[i][++tot[i]] = T;
}
}
for(int i = 1; i <= n; i++) {
for(int T = 1; T <= tot[i]; T++) {
if(cnt[i][T] >= s) break;
++sum[i];
}
}
//cout << "--------" << '
';
//for(int i = 1; i <= n; i++) cout << sum[i] << ' ' << tot[i] << '
';
while(q--) {
int x; ll k; cin >> x >> k;
if(k <= (ll)sum[x]) {
cout << cnt[x][k] << '
';
continue;
}
if(tot[x] == sum[x]) {
cout << -1 << '
';
continue;
}
k -= sum[x];
dbg(k);
ll ans = s + 1ll * len * ((k - 1) / (tot[x] - sum[x]));
dbg(ans);
int r = k - 1ll * (tot[x] - sum[x]) * ((k - 1) / (tot[x] - sum[x]));
dbg(r, sum[x]);
ans += cnt[x][r + sum[x]] - s;
cout << ans << '
';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
while(cin >> n >> m >> q) run();
return 0;
}