AIM Tech Round Div 1
春节过完急急忙忙先来水几道题...
A:题意:给定由一个字符串凿出一个图的过程:字符串仅有a,b,c三种字符,造出的图中第i个顶点表示原来的第i个字符,i与j有连边当且仅当s[i]与s[j]相同或者s[i]与s[j]是相邻的字符((a,b),(b,c)),(a,c)不算。现在给出由某个字符串造出的图,构造一个符合要求的字符串。
我码了90行显然有问题....
我的做法是先看看是不是由一种字符组成的(这种情况可以包含仅有两种相邻字符的情况),这种情况直接看是不是完全图就可以了。
不是上面那种情况的话我们观察到b字符一定和所有点有连边,那么b就全被找出来了,我们在找到两个没连边的点,那么他们一定分别是a和c,再把与他们相连却又不是b的点标成自己的字符。
(我天真的认为这就完了,但是还有许多情况没有考虑,注意到有无解的情况,所以我们要对划出来的a,c扫一遍,所有的a必须构成团,c同理,并且a和c之间不能有任何连边) 。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
int n,m,a,b,A,B,num,c[510];
char mark[510];
bool list[510][510];
bool check() {
for(int i = 1;i <= n;i ++)
for(int j = i + 1;j <= n;j ++)
{
if(mark[i] == mark[j] && !list[i][j])
{
return false;
}
if(abs(mark[j] - mark[i] == 1) && !list[i][j])
{
return false;
}
if(abs(mark[j] - mark[i] == 2) && list[i][j])
{
return false;
}
}
return true;
}
int main() {
scanf("%d%d",&n,&m);
if(m == (n * (n - 1)) / 2)
{
printf("Yes\n");
for(int i = 1;i <= n;i ++) printf("a");
return 0;
}
for(int i = 1;i <= m;i ++)
{
scanf("%d%d",&a,&b);
list[a][b] = list[b][a] = true;
c[a] ++;
c[b] ++;
}
for(int i = 1;i <= n;i ++)
if(c[i] == n - 1)
{
mark[i] = 'b';
num ++;
}
for(int i = 1;i <= n;i ++)
{
bool F = false;
for(int j = i + 1;j <= n;j ++)
{
if(list[i][j] == false)
{
mark[i] = 'a';
mark[j] = 'c';
num += 2;
A = i;
B = j;
F = true;
break;
}
}
if(F) break;
}
for(int i = 1;i <= n;i ++)
if(list[A][i] && mark[i] != 'b')
{
mark[i] = 'a';
num ++;
}
for(int i = 1;i <= n;i ++)
if(list[B][i] && mark[i] != 'b')
{
mark[i] = 'c';
num++;
}
if(num != n || ! check()) printf("No");
else
{
printf("Yes\n");
for(int i = 1;i <= n;i ++) printf("%c",mark[i]);
}
return 0;
}
B:题意:给出一个序列,你需要使用以下两种操作使得序列的所有数的gcd不为1.
1:删除一段连续的子序列(不能删完),代价为a*删除的长度。
2:把一个点上的数修改最大1(+1或者-1),每修改一个数代价为b,每个数只能修改1次。(a,b给出)。
想了好久好久,首先可以看出是一个Dp,但是在最终gcd不确定的情况下仿佛很难Dp,而最终gcd仿佛很多。
问题就在于这个最终gcd上,首先,因为题目中不能把序列删完,所以说头或者尾一定是留下的!其次因为题目要求仅仅是gcd不为1,那么其实最后gcd是多少都行(只要不是1) ,所以我们只用考虑A[1]-1,A[1],A[1]+1,A[n],A[n]-1,A[n]+1的质因子就行了!
然后就简单了,我们设Dp[i][0/1/2]表示到了第i个数,子序列还没删,正在删,删完了的情况就行了。
注意long long
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
#define inf 1e17
#define Int long long
using namespace std;
Int a,b,Dp[1000010][3],Ans = inf;
int n,A[1000010],c[10];
Int calc(int x,int M) {
if(x % M == 0) return 0;
if((x + 1) % M == 0 || ((x - 1) % M == 0 && x >= 2)) return b;
return inf;
}
Int work(int M) {
for(int i = 1;i <= n;i ++)
{
Dp[i][0] = inf;
Dp[i][1] = inf;
Dp[i][2] = inf;
Dp[i][0] = Dp[i - 1][0] + calc(A[i],M);
Dp[i][1] = min(Dp[i - 1][1],Dp[i - 1][0]) + a;
Dp[i][2] = min(Dp[i - 1][1],Dp[i - 1][2]) + calc(A[i],M);
Dp[i][0] = (Dp[i][0] > inf) ? inf:Dp[i][0];
Dp[i][1] = (Dp[i][1] > inf) ? inf:Dp[i][1];
Dp[i][2] = (Dp[i][2] > inf) ? inf:Dp[i][2];
}
Int ret = inf;
ret = min(ret,min(Dp[n][0],Dp[n][2]));
if(Dp[n][1] != n * a) ret = min(ret,Dp[n][1]);
return ret;
}
int main() {
scanf("%d",&n);
scanf("%I64d%I64d",&a,&b);
for(int i = 1;i <= n;i ++) scanf("%d",&A[i]);
c[1] = A[1];c[2] = A[n];
c[3] = A[1] - 1;c[4] = A[1] + 1;
c[5] = A[n] - 1;c[6] = A[n] + 1;
for(int i = 1;i <= 6;i ++)
{
for(int p = 2;p * p <= c[i];p ++)
{
if(c[i] % p == 0)
{
while(c[i] % p == 0) c[i] /= p;
Ans = min(Ans,work(p));
}
}
if(c[i] != 1) Ans = min(Ans,work(c[i]));
}
printf("%I64d",Ans);
}
C:题意:给出n个点(n<=100000),每个点用坐标表示,现在对于每个点,你可以把它的横坐标或者纵坐标变成0,问通过这样的操作,两两点之间的距离最大值最小是多少。
这道题真的是调死了,首先有一个显而易见的性质,如果我们把第i个点投在横轴上,那么对于所有的点j,只要有|x[j]|<=|x[i]|,我们就也把它投到横轴上,因为这样一来计算答案的时候肯定还是用i,但是又减少了纵轴的点,只好不坏。
然而还是不太好做的样子,因为除了横轴与纵轴的关系,同一根轴上的点之间也可能有距离,想到这个就可以想到二分答案了,我们二分最长的距离(其实求最大值最小本来就很可能是二分答案),然后对于每个点i,考虑把它投在横轴上的情况,如果此时x[i]<=0,那么我们找到所有的x[j]>x[i]&&dis[j][i]<Ans,把这些点投在横轴上,再把剩下的点投在纵轴上。我们再考虑x[i]>0,那么我们找到所有的x[j]<x[i]&&dis[j][i]<Ans,把这些点投在横轴上。(以上两种情况都还有一个条件,就是所有的点j都还必须满足|x[j]|<=|x[i]|,因为j点不能影响到i点对答案的计算),我们只要用个队列就可以实现o(n)求每个点在当前Ans下的答案了(我还记录了前缀min,max,后缀min,max)。
越调试越复杂,代码也是越调越丑....
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
#define inf 2ll * 1e18
#define Int long long
using namespace std;
struct point{Int x;Int y;
};point Point[100010];
int n,m;
Int Ans = 1e18,pre_maxx[100010],bac_maxx[100010];
Int bac_minx[100010],pre_minx[100010];
bool comp(const point &x,const point &y) {
if(x.x != y.x) return x.x < y.x;
return x.y > y.y;
}
bool check(Int x) {
int r = 0,s = 0,l = 1;
for(int i = 1;i <= n;i ++)
{
if(Point[i].x >= 0) {s = i;break;}
while(r < i) r ++;
while(r < n && (Point[r + 1].x - Point[i].x) * (Point[r + 1].x - Point[i].x) <= x && abs(Point[r + 1].x) <= abs(Point[i].x)) r ++;
while(r > i && abs(Point[r].x) > abs(Point[i].x)) r --;
if(i == 1 && r == n) return true;
else
{
Int c = max(pre_maxx[i - 1],bac_maxx[r + 1]);
Int d = min(pre_minx[i - 1],bac_minx[r + 1]);
Int e = max(abs(Point[i].x),abs(Point[r].x));
if((c - d) * (c - d) <= x && e * e + d * d <= x && e * e + c * c <= x)
return true;
}
}
if(s == 0) s = n + 1;
for(int i = s;i <= n;i ++)
{
while(l < i && (Point[l].x - Point[i].x) * (Point[l].x - Point[i].x) > x) l ++;
while(l < i && abs(Point[l].x) > abs(Point[i].x)) l ++;
while(l > 1 && abs(Point[l - 1].x) <= abs(Point[i].x) && (Point[l - 1].x - Point[i].x) * (Point[l - 1].x - Point[i].x) <= x) l --;
if(i == n && l == 1) return true;
else
{
Int c = max(pre_maxx[l - 1],bac_maxx[i + 1]);
Int d = min(pre_minx[l - 1],bac_minx[i + 1]);
Int e = max(abs(Point[l].x),abs(Point[i].x));
if((c - d) * (c - d) <= x && e * e + d * d <= x && e * e + c * c <= x)
return true;
}
}
return false;
}
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%I64d%I64d",&Point[i].x,&Point[i].y);
sort(Point + 1,Point + n + 1,comp);
bac_maxx[n + 1] = -1ll * inf;
bac_minx[n + 1] = inf;
Int A,B,C,D;
A = C = -1ll * inf;
B = D = inf;
for(int i = n;i >= 1;i --)
{
A = max(A,Point[i].x);
B = min(B,Point[i].x);
C = max(C,Point[i].y);
D = min(D,Point[i].y);
bac_maxx[i] = max(Point[i].y,bac_maxx[i + 1]);
bac_minx[i] = min(Point[i].y,bac_minx[i + 1]);
}
bac_maxx[0] = bac_maxx[1];
bac_minx[0] = bac_minx[1];
pre_maxx[1 - 1] = -1ll * inf;
pre_minx[1 - 1] = inf;
for(int i = 1;i <= n;i ++)
{
pre_maxx[i] = max(Point[i].y,pre_maxx[i - 1]);
pre_minx[i] = min(Point[i].y,pre_minx[i - 1]);
}
pre_maxx[n + 1] = pre_maxx[n];
pre_minx[n + 1] = pre_minx[n];
Int head = 0,tail = inf,T = 100;
while(head != tail)
{
Int Mid = (head + tail) >> 1;
if(check(Mid)) tail = Mid;
else head = Mid + 1;
}
cout<<min(head,min((B - A) * (B - A),(D - C) * (D - C)));
return 0;
}
D:有i个人,抓到第i个人的几率是百分之a[i],现在你每抓一个人就要猜一次这个人是谁,游戏结束当且仅当每个人你都曾抓到并且猜中是他。问期望多少步游戏结束。(n<=100)
这道题反而压力没有上一道题那么大,一个概率问题的通用想法,算出很多步之后的答案,再往后的因为太小就可以忽略了,这道题也是这样,A[i]表示在第i步结束的概率,我们再设p[i]为抓到i的概率,q[i]=1-p[i],k[i]为猜抓到的人是i的次数。其中有A[t]=(1-q[1]^k[1])*(1-q[2]*k[2])....(1-q[n]*k[n]).,其中k[1]+k[2]+...k[n]=t。(就相当于用1减去每次猜i都没有猜中的几率,就是至少猜中了一次的几率了)。
那么答案=(A[1]-A[0])*1+(A[2]-A[1])*2+.....(A[T]-A[T-1])*T,T是自己定的常数。
最后的问题就在于如何把t分在每一个k[]里使得A[t]最大,其实这个贪心就可以了,(很明显的吧..),我们看当前(1-q[i]*(1-p[i])) / (1-q[i])最大的那个i,把它的q[i]乘上(1-p[i])就可以了。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
using namespace std;
double f[300010],p[110],q[110],k[110],Ans = 0;
int n,T;
int main() {
scanf("%d",&n);
for(int i = 1;i <= n;i ++) cin>>p[i],q[i] = 1.0;
for(int i = 1;i <= n;i ++) p[i] /= 100.0;
T = 500000;
double last = 0;
for(int i = 0;i <= T;i ++)
{
double x = 1,ret = -100.0;int M;
for(int j = 1;j <= n;j ++) x = x * (1.0 - q[j]);
Ans += (x - last) * (double)i;
last = x;
for(int j = 1;j <= n;j ++)
if((1.0 - q[j] * (1.0 - p[j])) / (1.0 - q[j]) > ret)
{
ret = (1.0 - q[j] * (1.0 - p[j])) / (1.0 - q[j]);
M = j;
}
q[M] *= (1.0 - p[M]);
}
printf("%0.10f",Ans);
return 0;
}
E题多项式,弃掉.....
话说这场真的好难,幸好当时没打...