转自: http://blog.csdn.net/summonlight
这几天又把China-Final的题补了一些,比较蛋疼的是为啥根本搜不到题解。。。我觉得我要多加些关键字,2016 ICPC China-Final, codeforces gym101194。题目在cf可以交,因为只有PDF就不每一题都加链接了。http://codeforces.com/gym/101194
A. Number Theory Problem
7的二进制表示为111,所以111, 1110(111移位的结果)都能被7整除。而
2k−1
的二进制表示为
11…1
,共
k
个1。所以只需要
k
被3整除即可。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
int main()
{
int T,n;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d",&n);
printf("Case #%d: %d\n",t,n/3);
}
return 0;
}
C. Mr. Panda and Strips
这题据说比赛的时候
O(n4)
加优化就能过。。。反正我们也没试。。。
我最开始看到的是出题人的题解,消化了很久并且按照他讲的写了,结果WA了以后仔细改了改又TLE了。然后再回去看他的题解,感觉错误不止一个啊。。。= =
我出于无奈只好直接问q巨要了一份代码。。。
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int MAXN=1005;
const int MAXV=100005;
int c[MAXN],r[MAXN];
bool vis[MAXV];
vector<int>loc[MAXV];
set<pair<int,int> >st;
multiset<int>len;
inline int getMax()
{
return (len.empty() ? 0 : *(--len.end()));
}
inline void setBlock(int x)
{
auto itr=st.lower_bound(make_pair(x+1,0));
if(itr==st.begin())return;
itr--;
int ml=x,mr=x;
while(1)
{
if(itr->second<x)break;
len.erase(len.lower_bound(itr->second-itr->first+1));
ml=min(ml,itr->first);
mr=max(mr,itr->second);
if(itr==st.begin())
{
st.erase(itr);
break;
}
else st.erase(itr--);
}
if(ml<x && st.find(make_pair(ml,x-1))==st.end())
st.insert(make_pair(ml,x-1)),len.insert(x-ml);
if(mr>x && st.find(make_pair(x+1,mr))==st.end())
st.insert(make_pair(x+1,mr)),len.insert(mr-x);
}
int main()
{
int T;
scanf("%d",&T);
for(int ca=1;ca<=T;ca++)
{
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&c[i]);
loc[c[i]].push_back(i);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)vis[c[j]]=0;
r[i]=i-1;
while(r[i]<n && !vis[c[r[i]+1]])vis[c[++r[i]]]=1;
}
int res=0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)vis[c[j]]=0;
st.clear(),len.clear();
for(int j=i+1;j<=n;j++)
{
st.insert(make_pair(j,r[j]));
len.insert(r[j]-j+1);
}
res=max(res,getMax());
for(int j=i;j>=1;j--)
{
if(vis[c[j]])break;
vis[c[j]]=1;
for(int k=0;k<(int)loc[c[j]].size();k++)
if(loc[c[j]][k]>i)setBlock(loc[c[j]][k]);
res=max(res,getMax()+(i-j+1));
}
}
printf("Case #%d: %d\n",ca,res);
for(int i=1;i<=n;i++)
loc[c[i]].clear();
}
return 0;
}
这个代码的意思大概就是先预处理每个颜色分布在哪几个位置,每个位置最多能延伸的区间。然后枚举第一个区间的右边界
i
,再枚举左边界
j
,
j
每减小1,就用新加入的颜色
a[j]
在之后出现的位置
x(即a[x]==a[j], x>j)
去截保存在set里的一些区间(所有满足
l≤x≤r
的区间)。对于每一个
x
,在全截完了以后再插入在
x
左边的最长的
[ml,x−1]
,在
x
右边的最长的
[x+1,mr]
。代码中multiset<int> len
是辅助的保存长度的有序列。
这个算法对于每一个
i
都是先插入
O(n)
个区间,再删除和插入
O(n)
个区间,显然复杂度是
O(n2logn)
。不过时间达到了3.5s,原因是常数太大了,操作的区间总个数更接近于
4n
。
后来又看到一份代码(是cf上的vj号交的,所以也不知道是哪位大侠)感觉很不错,跟这个其实是一个思路的,于是就学习了一下。
预处理时采用了时间戳的int数组,也可以像上面那样每次清空bool数组。
#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
const int N=1005,MAX=1e5+5;
vector<int> p[MAX];
int a[N],dp[N][N];
bool isok[N][N];
int vis[MAX];
set<PII> ival;
multiset<int> q;
void split(int x)
{
auto it=ival.lower_bound(PII(x+1,0));
if (it==ival.begin()) return;
it--;
int a=it->first,b=it->second;
ival.erase(it);
q.erase(q.find(dp[a][b]));
if (a<x) {
ival.insert(PII(a,x-1));
q.insert(dp[a][x-1]);
}
if (x<b) {
ival.insert(PII(x+1,b));
q.insert(dp[x+1][b]);
}
}
int main()
{
int T,n;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d",&n);
memset(vis,0,sizeof(vis));
for (int i=1;i<MAX;i++) {
p[i].clear();
}
for (int i=1;i<=n;i++) {
scanf("%d",&a[i]);
p[a[i]].push_back(i);
}
for (int i=1;i<=n;i++) {
bool f=true;
for (int j=i;j<=n;j++) {
if (vis[a[j]]==i) {
f=false;
}
vis[a[j]]=i;
isok[i][j]=f;
}
}
for (int len=1;len<=n;len++) {
for (int i=1;i+len-1<=n;i++) {
int j=i+len-1;
if (isok[i][j]) {
dp[i][j]=j-i+1;
} else {
dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
}
}
}
int ans=-1;
memset(vis,0,sizeof(vis));
for (int i=1;i<=n;i++) {
ival.clear();q.clear();
ival.insert(PII(1,n));
q.insert(dp[1][n]);
for (int j=i;j<=n;j++) {
if (vis[a[j]]==i) {
break;
}
vis[a[j]]=i;
for (auto x:p[a[j]]) {
split(x);
}
int cur=j-i+1;
if (!q.empty()) {
cur+=*q.rbegin();
}
ans=max(cur,ans);
}
}
printf("Case #%d: %d\n",t,ans);
}
return 0;
}
这个方法更优的地方在于它并没有一定要保存合法的区间(如 [1,n] 通常不合法),它保存的是 相对于第一个区间合法的区间 ,以及 该区间内自我合法的最大长度 。
复杂度同样为 O(n2logn) ,但是实际操作区间接近 2n ,所以运行时间也大概是一半。
D. Ice Cream Tower
这题应该说比较容易。读懂题目应该可以想到答案具有单调性,所以可以二分答案。记二分当前结果为
m
,那么问题转化为判定能不能堆出
m
个塔。这个也比较容易,我们只要将冰淇淋按从小到大排序,一层一层贪心地堆就可以了,因为这个冰淇淋如果当前不能用上,那么后续更不可能用上。(从大到小堆我没有测试,据说也可以通过)
不想动态申请内存的可以像这样使用滚动数组。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
const int N=3e5+5;
int T,n,k;
ll b[N],to[N][2];
bool ok(int m)
{
int p=1;
int pre=0,now=1;
for (int j=1;j<=m;j++) {
to[j][pre]=b[p++];
}
for (int i=1;i<k;i++) {
for (int j=1;j<=m;j++) {
while (p<=n&&b[p]<to[j][pre]*2) p++;
if (p>n) return false;
to[j][now]=b[p++];
}
swap(pre,now);
}
return true;
}
int bs(int l,int r)
{
int res=0;
while (l<=r) {
int m=(l+r)>>1;
if (ok(m)) {
res=m;l=m+1;
} else {
r=m-1;
}
}
return res;
}
int main()
{
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d%d",&n,&k);
for (int i=1;i<=n;i++) {
scanf("%lld",&b[i]);
}
sort(b+1,b+1+n);
int ans=bs(1,n/k);
printf("Case #%d: %d\n",t,ans);
}
return 0;
}
E. Bet
。。。这题本身是容易的。。。但是。。。
记对第
i
个赌局下注
ci
,则题意就是对于答案集合
S
中的每个
i
都有
该式对 i 求和可得 ∑i∈Siaiai+bi<1 。接下来排个序贪个心就不用说了吧。。。然而boss却是精度问题。。。多少队伍交了10+发就是因为精度问题。。。反正听说有1-(1e-50)的= =
于是我又是问q巨要的代码。。。然后我感觉Java很厉害。。。
import java.math.BigInteger;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int T = sc.nextInt();
BigInteger[] up = new BigInteger[100];
BigInteger[] down = new BigInteger[100];
for (int t = 1; t <= T; t++) {
int n = sc.nextInt();
sc.nextLine();
for (int i = 0; i < n; i++) {
String str = sc.nextLine();
String[] da = str.split(":");
double x = Double.parseDouble(da[0]) * 1000;
double y = Double.parseDouble(da[1]) * 1000;
BigInteger a = BigInteger.valueOf((long) x);
BigInteger b = BigInteger.valueOf((long) y);
BigInteger g = a.gcd(b);
up[i] = a.divide(g);
down[i] = a.add(b).divide(g);
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (up[i].multiply(down[j]).compareTo(down[i].multiply(up[j])) > 0) {
BigInteger tmp = up[i];
up[i] = up[j];
up[j] = tmp;
tmp = down[i];
down[i] = down[j];
down[j] = tmp;
}
}
}
BigInteger su = BigInteger.ZERO;
BigInteger sd = BigInteger.ONE;
int ans = 0;
for (int i = 0; i < n; i++) {
BigInteger nu = su.multiply(down[i]).add(sd.multiply(up[i]));
BigInteger nd = sd.multiply(down[i]);
BigInteger g = nu.gcd(nd);
su = nu.divide(g);
sd = nd.divide(g);
if (su.compareTo(sd) < 0) {
ans += 1;
} else {
break;
}
}
System.out.println("Case #" + t + ": " + ans);
}
}
}
py写的短代码
from decimal import Decimal
T = int(input())
for t in range(1, T + 1):
n = int(input())
num = []
for i in range(n):
a, b = input().split(":")
num.append(Decimal(a) / (Decimal(a) + Decimal(b)))
num.sort()
tot = Decimal('0')
ans = 0
for i in num:
if (tot + i >= Decimal('1')):
break
tot = tot + i
ans = ans + 1
print("Case #" + str(t) + ": " + str(ans))
F. Mr. Panda and Fantastic Beasts
比赛的时候还什么都不会。。。现在知道可以用后缀数组或者后缀自动机做,先只学后缀数组吧。。。
这么多串第一步就是加特殊字符全连在一起,用倍增法实现SA。记第一个串为
S1
,对于起点在
S1
的每一个后缀
suffix(i)
,如果按字典序向前找到第一个不始于S1的后缀
suffix(p)
(位置越近重合长度越长),那么要想答案串在
S1
之外的串中不出现,长度至少要
lcp(suffix(i),suffix(p))+1
,同理向后找,找到
suffix(q)
,长度至少要
lcp(suffix(i),suffix(q))+1
,所以用两者的最大值更新答案长度。注意,这个答案长度不能超过
S1
剩下的部分!
The solution above is based on forever97’s.
至于
lcp
可以用ST实现的RMQ,但是因为本来就是线性向前向后找,实际上使用最原始的一连串
height
值的最小值就可以啦。这个方法反而跑得飞快,10s的题在cf上只用了400+ms,复杂度我不太会分析。。。= =
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=300030;
char s[MAXN];
int str[MAXN];
int sa[MAXN];
int t1[MAXN],t2[MAXN],c[MAXN];
int rk[MAXN],height[MAXN];
bool cmp(int *r,int a,int b,int l)
{
return r[a] == r[b] && r[a+l] == r[b+l];
}
void da(int str[],int sa[],int rank[],int height[],int n,int m)
{
n++;
int i, j, p, *x = t1, *y = t2;
for(i = 0; i < m; i++)c[i] = 0;
for(i = 0; i < n; i++)c[x[i] = str[i]]++;
for(i = 1; i < m; i++)c[i] += c[i-1];
for(i = n-1; i >= 0; i--)sa[--c[x[i]]] = i;
for(j = 1; j <= n; j <<= 1) {
p = 0;
for(i = n-j; i < n; i++)y[p++] = i;
for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;
for(i = 0; i < m; i++)c[i] = 0;
for(i = 0; i < n; i++)c[x[y[i]]]++;
for(i = 1; i < m; i++)c[i] += c[i-1];
for(i = n-1; i >= 0; i--)sa[--c[x[y[i]]]] = y[i];
swap(x,y);
p = 1;
x[sa[0]] = 0;
for(i = 1; i < n; i++)
x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p >= n)break;
m = p;
}
int k = 0;
n--;
for(i = 0; i <= n; i++)rank[sa[i]] = i;
for(i = 0; i < n; i++) {
if(k)k--;
j = sa[rank[i]-1];
while(str[i+k] == str[j+k])k++;
height[rank[i]] = k;
}
}
void solve(int lim,int n)
{
int ans=n+1,pos=-1;
for (int i=1;i<=n;i++) {
while (i<=n&&sa[i]>=lim) i++;
if (i>n) break;
int cur1=height[i];
for (int p=i-1;p&&sa[p]<lim;p--) {
cur1=min(cur1,height[p]);
}
int cur2=height[i+1];
for (int q=i+1;q<=n&&sa[q]<lim;q++) {
cur2=min(cur2,height[q+1]);
}
int cur=max(cur1,cur2);
if (cur<lim-sa[i]) {
if (cur+1<ans) {
ans=cur+1;
pos=sa[i];
}
}
}
if (pos==-1) {
puts("Impossible");
} else {
for (int i=0;i<ans;i++) {
putchar(str[pos+i]);
}
puts("");
}
}
int main()
{
int T,n;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
scanf("%d",&n);
int up=140,len=0,firstl;
for (int i=1;i<=n;i++) {
scanf("%s",s);
int nl=strlen(s);
if (i==1) firstl=nl;
for (int j=0;j<nl;j++) {
str[len++]=s[j];
}
str[len++]=up+i;
}
str[--len]=0;
da(str,sa,rk,height,len,up+n);
printf("Case #%d: ",t);
solve(firstl,len);
}
return 0;
}
warning: 不赞成使用 height[0]/height[n+1] 。
H. Great Cells
拆开来用贡献做实在是太神奇了。。。dp+容斥不太适合我的能力。。。
第一项相当于每个格子当一次 great cell 就计数一次,第二项相当于所有填法的总和。
第一项转化为每一个格子的贡献(当 great cell 的次数)。一个格子为 great cell 当且仅当这个格子填 i 时,它所在的行和列填的都是小于 i 的数( (i−1)N−1+M−1 ),其他格子随便填( K(N−1)(M−1) )。注意, N=1,M=1 需要另外讨论!
于是这道题成为了本场代码第二短的。。。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MO=1e9+7;
ll fpow(ll x,int n)
{
ll res=1;
while (n) {
if (n&1) {
res=res*x%MO;
}
n>>=1;
x=x*x%MO;
}
return res;
}
int main()
{
int T;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
ll n,m,k,ans=0;
scanf("%I64d%I64d%I64d",&n,&m,&k);
for (int i=1;i<k;i++) {
ans=(ans+fpow(i,n+m-2))%MO;
}
ans=ans*fpow(k,(n-1)*(m-1))%MO;
ans=ans*n%MO*m%MO;
ans=(ans+fpow(k,n*m))%MO;
if (n==1&&m==1) {
ans=(ans+1)%MO;
}
printf("Case #%d: %I64d\n",t,ans);
}
return 0;
}
L. World Cup
总共六场比赛,每场三种结果,暴力。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int> PII;
int match[][2]={{1,2},{1,3},{1,4},{2,3},{2,4},{3,4}};
struct Result {
int a,b,c,d;
Result() {
}
Result(int w,int x,int y,int z) {
a=w;b=x;c=y;d=z;
}
} r[1000];
int top,s[5];
void dfs(int dep)
{
if (dep>=6) {
r[top++]=Result(s[1],s[2],s[3],s[4]);
return;
}
s[match[dep][0]]+=3;
dfs(dep+1);
s[match[dep][0]]-=3;
s[match[dep][1]]+=3;
dfs(dep+1);
s[match[dep][1]]-=3;
s[match[dep][0]]+=1;
s[match[dep][1]]+=1;
dfs(dep+1);
s[match[dep][0]]-=1;
s[match[dep][1]]-=1;
}
int count(int a,int b,int c,int d)
{
int res=0;
for (int i=0;i<top;i++) {
if (r[i].a==a&&r[i].b==b&&r[i].c==c&&r[i].d==d) res++;
}
return res;
}
int main()
{
top=0;
memset(s,0,sizeof(s));
dfs(0);
int T;
scanf("%d",&T);
for (int t=1;t<=T;t++) {
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
printf("Case #%d: ",t);
int cnt=count(a,b,c,d);
if (cnt==0) puts("Wrong Scoreboard");
else if (cnt==1) puts("Yes");
else puts("No");
}
return 0;
}
在此特别感谢博主 summonlight