闲话
感觉这场最难的就是D,到现在都不太理解
A - Very Very Primitive Game
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
int a,b,c;cin >> a >> b >> c;
while(1)
{
if(c == 0)
{
if(a == 0) return cout << "Aoki",0;
--a;
}
else
{
if(b == 0) return cout << "Takahashi",0;
--b;
}
c ^= 1;
}
return 0;
}
B - Magic 3
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
int n,s,d;cin >> n >> s >> d;
forn(i,1,n)
{
int x,y;cin >> x >> y;
if(x < s && y > d) return cout << "Yes",0;
}
cout << "No";
return 0;
}
C - Bowls and Dishes
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
typedef pair<int,int> pii;
#define x first
#define y second
const int N = 105;
pii need[N],p[N];
bool st[N];
int main()
{
int n,m;scanf("%d%d",&n,&m);
forn(i,1,m) scanf("%d%d",&need[i].x,&need[i].y);
int k;scanf("%d",&k);
forn(i,1,k) scanf("%d%d",&p[i].x,&p[i].y);
int res = 0;
forn(S,0,(1 << k) - 1)
{
forn(i,1,n) st[i] = 0;
forn(j,0,k - 1)
{
if(S >> j & 1) st[p[j + 1].x] = 1;
else st[p[j + 1].y] = 1;
}
int tmp = 0;
forn(i,1,m) if(st[need[i].x] && st[need[i].y]) ++tmp;
res = max(res,tmp);
}
printf("%d",res);
return 0;
}
D - Staircase Sequences
题目大意:给定一个\(n\)求公差为\(1\)的等差数列且和为\(n\)的方案数.
数据范围:\(1 \leq N \leq 10^{12}\)
思路
设这个等差数列首项为\(x\)末项为\(y\)则有\((x+y)*(y - x + 1) = 2 * N\).那么根据复杂度反推,可能是个根号算法,可以分解\(2*N\)的约数\(d\),那么枚举\(d\)的同时\(N/d\)也是一个合法的约数,此时有两种情况:要么\(d=(x+y)\)此时可以反推出一个条件,要么\(d=(y-x+1)\)此时也可以反推出一个条件,根据条件判断当前情况是否合法,如果合法就累加.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
int main()
{
ll N;cin >> N;
ll n = 2 * N;
ll res = 0;
for(ll i = 1;i <= n / i;++i)
{
if(n % i == 0)
{
// cout << i << " " << n / i << endl;
ll d = i;
if((d * d + d - n) % (2 * d) == 0) ++res;
if((n - d * d + d) % (2 * d) == 0) ++res;
}
}
cout << res << endl;
return 0;
}
E - Magical Ornament
题目大意:有\(n\)个石头,编号从\(1\)开始,有\(m\)对关系表示一对石头可以摆放在相邻的位置,如果两个石头没有这样的关系则不可以放在相邻的位置.现在要做一个牛逼的法阵,这个法阵是一排连续的石头,要求相邻的石头满足关系,并且整个序列需要包含指定的\(k\)个牛逼石头,其编号由\(c_1,c_2...c_k\)指定.求一个合法的牛逼法阵最少需要多少个石头.
数据范围:
\(1 \leq n,m \leq 10^5\)
\(1 \leq k \leq 17\)
思路
思路肯定从特别小的\(k\)入手,因为要包含指定的\(k\)个牛逼石头,所以很自然的可以想到拿状压记录每个牛逼石头的选取状态.可以初步地把状态转移方程搞出来:
- 状态:\(f[S][i]\)表示当前选取的石头集合是\(S\),最后一个选取的牛逼石头是\(i\).
- 入口:\(f[1 << i][i] = 1\)只选取一个石头自己的时候
- 转移:枚举当前\(S\)中存在的一个石头\(j\)并且枚举将要加入这个集合的下一个石头\(i\),那么首先需要满足这两个变量与集合的关系,其次转移就是\(f[S | (1 <<i)][i] = min(f[S | (1 << i)][i],f[S][j] + dist(j,C[i]))\).因为这里有个问题:这个石头\(j\)他不一定是和下一个\(i\)可以直接相邻摆放的,但是并不意味着就不能放入\(i\)了,可以在\(j\)之后放入一些石头并且最终联通向\(i\),所以这里的\(dist(j,i)\)就表示从一个牛逼石头\(j\)连向任意一个石头\(i\)的距离(当然转移方程里面是指定的牛逼石头\(C_i\)才对).
- 出口:\(\min\limits_{i=1}^n{f[S][i]}\).
- 注意实际写代码的时候有所有编号调整到从\(0\)开始的操作.
现在的问题就是剩下一个\(dist\)的处理了,建图比较简单,如果两个石头之间存在可相邻关系那么就连一条权值为\(1\)的无向边.同时也不需要求任意两点的距离,牛逼石头最多\(17\)个,所以预处理出来每个牛逼石头的单源最短路就可以了.因为权值都是\(1\)所以跑个简单的BFS
就可以了.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 1e5+7,M = 2 * N,CK = 20,INF = 0x3f3f3f3f;
int dist[CK][N],c[CK];
int edge[M],succ[M],ver[N],idx;
int f[1 << 17][CK];
void add(int u,int v)
{
edge[idx] = v;
succ[idx] = ver[u];
ver[u] = idx++;
}
void bfs(int f)
{
memset(dist[f],0x3f,sizeof dist[f]);
queue<int> q;q.push(c[f]);dist[f][c[f]] = 0;
while(!q.empty())
{
int u = q.front();q.pop();
for(int i = ver[u];~i;i = succ[i])
{
int v = edge[i];
if(dist[f][v] > dist[f][u] + 1)
{
dist[f][v] = dist[f][u] + 1;
q.push(v);
}
}
}
}
int main()
{
memset(ver,-1,sizeof ver);
int n,m;scanf("%d%d",&n,&m);
forn(i,1,m)
{
int u,v;scanf("%d%d",&u,&v);
--u;--v;
add(u,v),add(v,u);
}
int k;scanf("%d",&k);
forn(i,0,k - 1) scanf("%d",&c[i]),--c[i];
forn(i,0,k - 1) bfs(i);
memset(f,0x3f,sizeof f);
forn(i,0,k - 1) f[1 << i][i] = 1;
forn(S,1,(1 << k) - 1)
{
forn(j,0,k - 1) forn(i,0,k - 1)
{
if(f[S][j] == INF) continue;
if(!(S >> j & 1) || dist[j][c[i]] == INF || (S >> i & 1)) continue;
f[S | (1 << i)][i] = min(f[S | (1 << i)][i],f[S][j] + dist[j][c[i]]);
}
}
int res = INF;
forn(i,0,k - 1) res = min(res,f[(1 << k) - 1][i]);
printf("%d",res == INF ? -1 : res);
return 0;
}
F - Shift and Inversions
思路
因为每次修改都是把第一个元素放到最后一个元素,所以直接树状数组维护一下值域查询就可以了.
逆序对是\(n^2\)级别的,需要开ll
.
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
const int N = 3e5+7;
int c[N],a[N];
int n;
inline int lowbit(int x)
{
return x & -x;
}
void modify(int x,int v)
{
for(int i = x;i < N;i += lowbit(i))
c[i] += v;
}
int query(int x)
{
int res = 0;
for(int i = x;i;i -= lowbit(i))
res += c[i];
return res;
}
int main()
{
scanf("%d",&n);
ll res = 0;
forn(i,1,n)
{
scanf("%d",&a[i]);
a[i]++;
res += query(N - 1) - query(a[i]);
modify(a[i],1);
}
printf("%lld\n",res);
forn(k,1,n - 1)
{
res -= query(a[k] - 1);
res += query(N - 1) - query(a[k]);
printf("%lld\n",res);
}
return 0;
}