Potato的暑期训练day#3 ——Codeforces Round #549 (Div. 2)
Potato的暑期训练day#3 ——Codeforces Round #549 (Div. 2)
题目链接:
A.http://codeforces.com/problemset/problem/1143/A
B.http://codeforces.com/problemset/problem/1143/B
C.http://codeforces.com/problemset/problem/1143/C
D.http://codeforces.com/problemset/problem/1143/D
E.http://codeforces.com/problemset/problem/1143/E
F.http://codeforces.com/problemset/problem/1143/F
A - The Doors
水题没什么好说的的
B - Nirvana
题意:给出一个n,要求求出小于n中的所有数的各位乘积的最大的是多少
思路:存下n的每一位,贪心地考虑每一个位置,将这个位置减1,这个位置之前的位保证最大,之后的位置保证全为9,注意特判最大位。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 25;
int num[maxn];
int main() {
int n,cnt = 0;
scanf("%d",&n);
while(n) {
num[cnt++] = n%10;
n /= 10;
}
int sum = 1,ans = -1;
for(int i = cnt - 1;i>=0;i--) sum *= num[i];
ans = sum;
for(int i = cnt - 1;i >= 0;i--) {
sum = 1;
for(int j = cnt - 1;j >= 0;j--) {
if(j>i) {
sum *= num[j];
}
else if(i == j) {
if(j == cnt - 1 && num[j] - 1 == 0) {
sum*=1;
}
else sum *= (num[j]-1);
}
else sum*=9;
}
ans = max(sum,ans);
}
printf("%d",ans);
return 0;
}
C - Queen
题意:给出一棵树,每个节点有两种状态,1时代表这个节点可能被删除,0表示这个节点不会会删除,当一个节点的所有儿子节点均为1状态时该节点可以删除,删除后他的儿子节点继承其父亲节点
思路:由于删除节点的所有儿子节点都是1,因此不会有影响,扫一遍即可。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5+5;
int n;
int par[maxn],c[maxn],vis[maxn];
vector<int> G[maxn];
void del(int s) {
int x = par[s];
for(int i = 0;i < G[s].size();i++) {
par[G[s][i]] = x;
}
}
void solve() {
int flag = 1;
for(int i = 1;i <= n;i++) {
if(c[i]) {
if(!vis[i]) {
del(i);
printf("%d ",i);
flag = 0;
}
}
}
if(flag) printf("-1");
}
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i++) {
scanf("%d%d",&par[i],&c[i]);
if(!c[i]) vis[i] = 1;
else {
G[par[i]].push_back(i);
}
}
for(int i = 1;i <= n;i++) if(c[par[i]] && !c[i]) vis[par[i]] = 1;
solve();
return 0;
}
D - The Beatles
题意:给出n,k,a,b;表示共有nk个城市,这些城市等距围成一个圆,共有n家餐厅,以1,k+1,2k+1.....均匀地分布在这个圆上,a表示当在起始点s的时候,距离餐厅最近的距离,b表示第一次移动l时距离餐厅最近的距离,已知我们以l的步长移动。
思路:等价于同余方程 $ l \equiv \pm a\pm b\ modk $ 我们可以解出 \(l = kx+l[i]\) 其中x为任意整数,l[i]代表ab的正负情况,总的移动步数 = nk/gcd(nk,l[i]+x*k) 我们从0-n-1枚举x就好,这样我们就求出了最大最小
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll gcd(ll a,ll b) {
return b ? gcd(b,a%b) : a;
}
ll l[4];
int main() {
ll n,k,a,b,now;
cin>>n>>k>>a>>b;
ll maxans = -1,minans = 1e18;
l[0] = (2*k+a+b)%k;
l[1] = (2*k+a-b)%k;
l[2] = (2*k-a+b)%k;
l[3] = (2*k-a-b)%k;
for(int i = 0;i < 4;i++) {
for(int j = 0;j < n;j++) {
now = n*k/gcd(n*k,l[i]+j*k);
maxans = max(maxans,now);
minans = min(minans,now);
}
}
cout<<minans<<" "<<maxans;
return 0;
}
E - Lynyrd Skynyrd
题意:给你两个排列a,p,p为1,2,3...n的一个排列,a是长度为m的一个任意排列,现在有q个查询,对于每个[l,r]的查询,表示在排列a 中的[l,r]这个连续序列是否存在子序列是p序列的一个连续段或者能够通过右移得到(也可以把p序列看成一个圆来匹配)
思路:对于A中的每一个Ai,求出 (Ai在P中的前一个数字)在A中的小于i的最大位置b[i]。比如P是(3,2,1),A是(3,1,2,3),那对于A中的2来说,求出来的b就是1,对于A中第二个3来说,求出来的b就是2,得到的b为(0,0,1,2)。可以通过记录每一个数字最后出现的位置来求每一个Ai的b[i]。
那对于一个A[i],我们算b[b[b[b[…b[i]]…],套n-1次就可以得到[L,i]这个区间存在以A[i]为结尾的循环右移排列需要的最大的L。直接套的话每一个数字都要算N-1次,是O(n²)的算法,所以我们用倍增的思想,f[i][j]代表第i个数字要得到以它为结尾的长度为2^j的排列所需要的最大的区间左端点L,那么显然f[i][0] = b[i],转移方程为f[i][j+1] = f[ f[i][j] ] [ j ],
然后用g[i]表示最大的L使得[L,i]这个区间存在P的循环右移,然后利用f[i][j]算一下第i个要到前n-1需要的最大的L,转移方程是g[i] = max(g[i-1],L)。
对于每个查询[L,R],只要判断L是不是比R大就可以知道[L,R]存不存在P的循环右移了。!
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 50;
int pre[maxn];
int n,m,q;
int p[maxn],a[maxn],pos[maxn];
int f[maxn][21];
int g[maxn];
int solve(int x,int num){//利用f[i][j]计算x到前num个数需要的最大左端点
for(int i = 20;i >= 0;--i){
if((1<<i)&num) x = f[x][i];
}
return x;
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i = 1;i <= n;++i) scanf("%d",&p[i]),pre[p[i]] = p[i-1];
pre[p[1]] = p[n];
for(int i = 1;i <= m;++i){
scanf("%d",&a[i]);
pos[a[i]] = i;//记录a[i]这个数字最后一次出现的位置
f[i][0] = pos[pre[a[i]]] ;//a[i]的第一个前驱的位置
for(int j = 0;j < 20;++j) {
f[i][j+1] = f[f[i][j]][j];
//a[i]的第2^(j+1)个前驱是a[i]的第2^j个前驱的第2^j个前驱
}
g[i] = max(g[i-1],solve(i,n-1));
}
while(q--){
int l,r;scanf("%d%d",&l,&r);
if(l<=g[r]) printf("1");
else printf("0");
}
return 0;
}
F - U2 CodeForces - 1143F
计算几何这种东西就丢给队友了(雾