noip55
T1
考场想法:一看就是个dp题,于是写了个两维的dp,一测样例,发现假了,没有考虑后边的比赛没有选所造成的负贡献,发现两维的不好搞,想了一下,那干脆直接一维,设 \(dp_{i}\) 表示最后一场为i时的最大贡献,后边不选所造成的负贡献,加加减减就出来了,然后我就脑抽了,没有打就觉得计算负贡献不好搞,实际就是减去一个求和公式。干脆打了个暴力走人。
30pts:直接 \(n^{2}\) dp。
dp部分就是考场上想的:设 \(dp_{i}\) 表示以 \(i\) 为最后一场时的最大的愉悦值,转移方程也挺简单:
后边的式子就是在减去 \(i,j\) 两场之间的比赛没选所造成的负贡献,或者直接用等差数列求和都行我是zz。
60pts:多了 \(a_{i}\) 随机的部分分,把枚举j的边界改成\(\max(0,i-3000)\) 即可,改小了wa,改大了TLE,并不知道zjx考场上怎么试出来的。
100pts:线段树/cdq分治维护单调栈。
T2
考场想法:没有什么想法。
20pts:暴力乱搞...
100pts:不会...
T3
考场想法:数学题,那就尝试打表找规律,发现不好打,于是干脆丢了个状压拿30pts走人。
考后wsn靠打表A了此题
30pts:暴力乱搞。
70pts:不会...
100pts:不会...
设 \(n=n-k+m,k=m\) ,那么答案就是:
Code
#include<cstdio>
#include<cctype>
#define re register
#define MAX 1000003
#define int long long
const int mod = 1e9+7;
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
auto fd = [](int a,int b) -> int { return b-mod+a>=0?b-mod+a:a+b; };
auto debug = []() { printf("fuck\n"); }; auto line = []() { printf("\n"); };
auto begin = []() { printf("begin\n"); }; auto end = []() { printf("end\n"); };
}using namespace some;
namespace OMA
{
int n,k,m,ans;
int fac[MAX],inv[MAX];
auto quickpow = [](int a,int b,int res = 1) -> int
{
while(b)
{
if(b&1)
{ res = res*a%mod; }
a = a*a%mod,b >>= 1;
}
return res;
};
auto C(int n,int m) -> int { return (n<m||n<0||m<0)?0:fac[n]*inv[n-m]%mod*inv[m]%mod; };
auto main = []() -> signed
{
freopen("perm.in","r",stdin); freopen("perm.out","w",stdout);
cin >> n >> k >> m;
fac[0] = inv[0] = 1,n -= k-m,k = m;
if(k==1)
{ printf("%lld\n",n-1); return 0; }
for(re int i=1; i<=n; i++)
{ fac[i] = fac[i-1]*i%mod; }
inv[n] = quickpow(fac[n],mod-2);
for(re int i=n-1; i; i--)
{ inv[i] = inv[i+1]*(i+1)%mod; }
for(re int i=1; i<=k; i++)
{ ans = fd(ans,C(n-i,k-i+2)); }
for(re int i=2; i<=n; i++)
{ ans = fd(ans,C(n-i-1,k-2)*(i-C(i-1,2)-1+mod)%mod); }
printf("%lld\n",ans);
return 0;
};
}
signed main()
{ return OMA::main(); }
T4
考试想法:直接生成树乱搞,就是个大板题,结果码了一会儿发现先算每条边的w显然是错的,于是就去骗分了,结果并查集写错了+考虑情况少了,实数的20pts没拿着,暴力加了个判断n的大小,少拿5pts。
没有直接输出”xxx“这种的,打暴力骗分就不要加范围特判了,容易出锅....
15pts:暴力乱搞。
35pts:多了实数的分,那直接跑kruskal即可,注意负数取绝对值可能比正数大,所以从大到小排序跑一遍,从小到大排序跑一遍。
100pts:
如果已知最后求出的复数的单位向量,那么要使得生成树边权和的模长最大,只需使各复数在该方向向量上的投影和最大。这样的和我们只需按照投影排序,跑最大生成树即可解决。
然而单位向量有很多,枚举是不太可行的。其实枚举非常可做
考虑通过两个复数在某个单位向量上的投影相等,来找出该单位向量。枚举复数对,这样就有了很多个单位向量,都存下来,排个序,然后就把坐标系划分成了好几部分,在空白区间内,没有别的向量再来卡,那么这个区间内,所有复数的投影的相对大小关系是不会变的。
再由kruskal可知:我们只关心边权的相对大小,并不在乎具体数值。
所以只需枚举对应的空白部分,在这里边瞎取一个,算出所有投影,排序,跑最大生成树,找出最优答案即可。
我的代码没给单位向量排序,是假的,需要给所有求出来的单位向量排个序,再去枚举空白部分。
本题综合考察了高考数学当中复数相关的知识,对选手综合素养要求较高,编不下去了
Code
#include<cmath>
#include<cstdio>
#include<cctype>
#include<algorithm>
const int N = 53;
const int M = 203;
#define re register
using std::sort;
const double pi = acos(-1);
namespace some
{
struct stream
{
template<typename type>inline stream &operator >>(type &s)
{
bool w=0; s=0; char ch=getchar();
while(!isdigit(ch)){ w|=ch=='-'; ch=getchar(); }
while(isdigit(ch)){ s=(s<<1)+(s<<3)+(ch^48); ch=getchar(); }
return s=w?-s:s,*this;
}
}cin;
int n,m;
double val,l,r,mid,ans;
template<typename type>inline type max(type a,type b){ return a>b?a:b; }
}using namespace some;
namespace MST
{
int fa[N];
int vala,valb;
struct EDGE
{
int u,v,a,b;
double w;
}path[M],p[M];
auto cmp1 = [](const EDGE &x,const EDGE &y) -> bool { return x.w>y.w; };
auto cmp2 = [](const EDGE &x,const EDGE &y) -> bool { return x.w<y.w; };
int find(int x) { return x!=fa[x]?fa[x] = find(fa[x]):fa[x]; }
auto merge = [](int x,int y) { int r1 = find(x),r2 = find(y); fa[r2] = r1; };
auto kruskal = []() -> void
{
vala = valb = 0;
for(re int i=1; i<=n; i++)
{ fa[i] = i; }
for(re int i=1,u,v; i<=m; i++)
{
u = path[i].u,v = path[i].v;
if(find(u)!=find(v)) { merge(u,v); vala += path[i].a,valb += path[i].b; }
}
ans = max(ans,sqrt(vala*vala+valb*valb));
vala = valb = 0;
for(re int i=1; i<=n; i++)
{ fa[i] = i; }
for(re int i=m,u,v; i; i--)
{
u = path[i].u,v = path[i].v;
if(find(u)!=find(v)){ merge(u,v); vala += path[i].a,valb += path[i].b; }
}
ans = max(ans,sqrt(vala*vala+valb*valb));
};
}using namespace MST;
namespace OMA
{
auto main = []() -> signed
{
freopen("mst.in","r",stdin);
freopen("mst.out","w",stdout);
cin >> n >> m;
for(re int i=1,u,v,a,b; i<=m; i++) { p[i] = path[i] = (EDGE){(cin >> u,u),(cin >> v,v),(cin >> a,a),(cin >> b,b)}; }
for(re int i=1; i<=m; i++)
{
for(re int j=i+1; j<=m; j++)
{
val = (1.0*(p[i].a-p[j].a))/(1.0*(p[j].b-p[i].b));
l = atan(val),r = l+pi,mid = (l+r)/2.0;
//printf("l=%0.6lf r=%0.6lf\n",l,r);
for(re int k=1; k<=m; k++) { path[k].w = 1.0*path[k].a*cos(mid)+1.0*path[k].b*sin(mid); }
sort(path+1,path+1+m,cmp1); kruskal();
//sort(path+1,path+1+m,cmp2); kruskal();
}
}
printf("%0.6lf\n",ans);
return 0;
};
}
signed main()
{ return OMA::main(); }
话说直接枚举角都能过,跑的还奇快,此题数据简直牛马。